logo

1 Set up R

  • Go to Github and download the repository as a zip file.
  • Move it to your local drive.
  • In RStudio go to File -> New Project -> Existing Directory -> Downloaded Github repository.
  • Run getwd() in your R console and check if the path ends in “Intro_to_R_gis”.
  • Open a new script: File -> New File -> R Script - this is where you will write all of today’s code.
  • Working in an R project means that our file paths are all relative to the “Intro_to_R_GIS” folder.

1.1 Quick R basics

  • R is case sensitive - read_csv is not the same as read_CSV.
  • New objects are created using the <- notation, e.g new_object <- 2 * 5 .
  • To overwrite an object use <- and its current name, e.g. current_object <- st_union(current_object).
  • Function’s arguments have to be in () and they’re defined with a =, e.g. st_transform(x = lfb_sf, crs = 27700)
  • To see function’s documentation preceed its name with a ?, e.g ?st_as_sf.
  • Write in the code editor (script) and execute your code line by line.
  • Use Ctrl + Enter to execute the current line of code.

1.2 Install & load R libraries

If you have not installed the necessary packages run install.packages() and then load them using the library() function.

install.packages("sf", dependencies = TRUE, type = "win.binary")
install.packages("tmap", dependencies = TRUE, type = "win.binary")
install.packages("tidyverse", dependencies = TRUE, type = "win.binary")

library(sf) 
library(tmap)
library(tidyverse)

2 Aims

By the end of today you will:

  • Understand what spatial data and GIS are.
  • Know how to use the Open Geography Portal.
  • Be aware of map projections and Coordinate Reference Systems (CRS) and be able to modify them.
  • Be able to load spatial data into R using the sf library.
  • Be familiar with using GSS codes to join statistics to geographies.
  • Understand how spatial objects can be manipulated using R’s tidyverse.
  • Understand how to use spatial joins.
  • Know how to make static and interactive maps in tmap.
  • Be able to export your maps and shapefiles.

3 GIS & Spatial Data

GIS stands for Geographic Information System/Science - the System part refers to the software used for capturing, storing, and manipulating spatial data, while the Science definition is concerned with scientific principles behind spatial analysis, and developing new methods and approaches to extract insight from geographic data.

Spatial Data - any data set which has, or has the potential to have, location attached to it. This includes, but is not limited to, coordinates, addresses, and geography codes.

Spatial data types:

  • Vector - points, lines, and polygons used to represent physical and administrative features. Used for displaying data with well defined extent.
  • Raster - pixel based data, often derived from satellite imagery. Used for displaying continuous or fuzzy variables.

Common spatial data formats:

  • Shapefile (.shp)
  • GeoPackage (.gpkg) / Geodatabase (.gdb)
  • GeoJSON / TopoJSON
  • Well-known-text
  • GeoTiff

3.1 Map Projection and Coordinate Reference System

In cartography, a map projection is a way to flatten a globe’s surface into a plane in order to make a map. This requires a systematic transformation of the latitudes and longitudes of locations from the surface of the globe into locations on a plane. All projections of a sphere on a plane necessarily distort the surface in some way and to some extent. Depending on the purpose of the map, some distortions are acceptable and others are not; therefore, different map projections exist in order to preserve some properties of the sphere-like body at the expense of other properties. Every distinct map projection distorts in a distinct way, by definition.

Source: Wikipedia

Source

All you need to know for today is:

  • When working with GB data use the British National Grid (BNG).
  • BNG uses Eastings and Northings which are given as metres, offset from the origin point.
  • BNG’s EPSG code is 27700.

3.2 GIS and R

R is commonly used for statistical analysis and programming, however it also has a whole range of GIS tools. There is a long history of geospatial libraries being developed for R and an amazing community of researchers and programmers around it. In the last few years, working with spatial data became much easier in R, with the development of the sf package. sf keeps all spatial information for each observation in a geometry column which means that we can treat it like a normal data frame but also perform all types of spatial operations on the data.

Simple feature collection with 6 features and 2 fields
geometry type:  MULTIPOLYGON
dimension:      XY
bbox:           xmin: 543417.3 ymin: 183488.5 xmax: 551943.8 ymax: 191137.3
epsg (SRID):    NA
proj4string:    +proj=tmerc +lat_0=49 +lon_0=-2 +k=0.9996012717 +x_0=400000 +y_0=-100000 +datum=OSGB36 +units=m +no_defs
     wd19cd         wd19nm                       geometry
1 E05000026          Abbey MULTIPOLYGON (((544338.3 18...
2 E05000027         Alibon MULTIPOLYGON (((549604.1 18...
3 E05000028      Becontree MULTIPOLYGON (((547563.4 18...
4 E05000029 Chadwell Heath MULTIPOLYGON (((548881 1910...
5 E05000030      Eastbrook MULTIPOLYGON (((551552.9 18...
6 E05000031       Eastbury MULTIPOLYGON (((547271.2 18...

3.3 Sources of spatial data

When working with most types of data you will most commonly need to link them to official administrative and census boundaries, as well as more general geographic data. Most national and local governments will have a geography portal where boundaries can be downloaded. At ONS boundaries, lookups, and documentation can be downloaded from the Open Geography Portal, while other geographic featues can be accessed from the Ordnance Survey.

Boundaries managed by the ONS come in several resolutions (more generalised boundaries sacrifice accuracy and precision for file size and processing speeds):

  • F - Full resolution
  • G - Generalised
  • U - Ultra generalised

and extents (extent determines where the land/water boundary is - extent of the realm includes areas of water):

  • C - Clipped to coastline
  • E - Extent of the realm

3.3.1 Exercise - Open Geography Portal

  • Go to the Open Geography Portal and download the 2019 Local Authority District BGC boundaries. Save them to data/shp in your project structure and unzip.
  • Open the folder and see how many different files there are.

4 London Fire Brigade Animal Rescue Data

Troughout this tutorial we will be using data from the London Fire Brigade - LFB Animal Rescue Data. It covers all incidents between 2009 and 2020 which included assistance to animals that may be trapped or in distress. The data is updated monthly and includes a range of variables for each incident including some location information (postcode, borough, ward),the date/time of the incidents, cost, and type of animal in trouble.

We want to visualise, and better understand how much money has been spent on animal related incidents between 2009 and 2020, and what the distribution is at the MSOA level of geography. To achieve this we will have to import spatial data, manipulate it and create summary statistics, and then plot it.

4.1 Loading spatial and non-spatial data

LFB data has been tidied up and saved as a Comma Separated Value file (.csv). We can use read_csv to open it in R.

4.1.1 Exercise - open LFB data

  • Create a new object called lfb by using read_csv(). Load data located in “data/csv/lfb_2009_2020.csv”.
  • Use glimpse() or head() to view lfb structure.

Solution

lfb <- read_csv("data/csv/lfb_2009_2020.csv")
head(lfb)

lfb is currently just a data frame. It has not got an explicit geometry column which links observations to their geographic location. It does however contain several columns which can be used to convert it into a spatial data format.

Ward_code column references the GSS codes of wards within which the observations fall. GSS codes can be used to join lfb data to boundaries from the Open Geography Portal. One issue with this particular column is that it does not indicate the currency of GSS codes. Wards are subject to frequent change, and as such it is best practice to be clear about the dates of any boundaries used by stating the exact code used, e.g. wd19cd. Because LFB data does not include this information we have no guarantee that the boundaries and GSS codes we join will match.

Fortunately we have also been provided with columns recording the easting, and northing of each incident. We can use those to convert lfb into an sf object. To achieve this we will use the st_as_sf() function which takes the following arguments:

new_object <- st_as_sf(x = input_data_frame, coords = c("x_coordinate_column", "y_coordinate_column"), crs = 27700)

4.1.2 Exercise - create spatial data

  • Create a new object called lfb_sf by converting lfb using the st_as_sf() function.
  • Use glimpse() or head() to view lfb_sf structure.

Solution

lfb_sf <- st_as_sf(x = lfb, coords = c("easting", "northing"), crs = 27700)
head(lfb_sf)
Simple feature collection with 6 features and 10 fields
geometry type:  POINT
dimension:      XY
bbox:           xmin: 504650 ymin: 164950 xmax: 554650 ymax: 192350
epsg (SRID):    27700
proj4string:    +proj=tmerc +lat_0=49 +lon_0=-2 +k=0.9996012717 +x_0=400000 +y_0=-100000 +ellps=airy +towgs84=446.448,-125.157,542.06,0.15,0.247,0.842,-20.489 +units=m +no_defs

st_as_sf() converted the easting and northing columns to simple feature geometries and created a new column called geometry which holds spatial information for each row. Now that lfb is a spatial object we can plot it using the tmap package. For now we will use the qtm() function which creates a quick map, using tmap's default settings. qtm() only needs to be supplied with a simple feature object and is very useful for quickly inspecting your data.

4.1.3 Exercise - quick static maps

  • Plot lfb_sf using the qtm() function.

Solution

qtm(lfb_sf)

We can also create interactive maps using the tmap, package by running tmap_mode("view") before executing qtm(). To reverse it and go back to static maps use tmap_mode("plot").

4.1.4 Exercise - quick interactive maps

  • Make an interactive map of lfb_sf using the qtm() function and tmap_mode("view").

Solution

tmap_mode("view")
qtm(lfb_sf)

4.2 Filtering by GSS code

It looks like some of the locations are located outside of London, however we are only interested in incidents within the Local Authority Districts making up Greater London. To remove all points outside of London we will have to first import the LAD boundaries which we downloaded from the Open Geography Portal and then use them to spatially filter lfb_sf data.

So far we have created our own sf objects by adding a geometry column. The LAD data set is already a spatial one and as such we can use the st_read() function from the sf package to import it. st_read is extremely versatile and able to import most spatial data formats into R. The only argument that needs to be supplied to st_read is the full path to the LAD boundaries

4.2.1 Exercise - loading shapefiles

  • Use st_read() to load the LAD boundaries you downloaded at the beginning of the tutorial, aslad_2019.
  • LAD path - data/shp/Local_Authority_Districts_December_2019_Boundaries_UK_BGC/ Local_Authority_Districts_December_2019_Boundaries_UK_BGC.shp
  • Make a static map of the object you have just created using qtm() and setting tmap_mode("plot").

Solution

lad_2019 <- st_read("data/shp/Local_Authority_Districts_December_2019_Boundaries_UK_BGC/Local_Authority_Districts_December_2019_Boundaries_UK_BGC.shp")
Reading layer `Local_Authority_Districts_December_2019_Boundaries_UK_BGC' from data source `D:\1_projects\Intro_to_geography\intro_to_gis\data\shp\Local_Authority_Districts_December_2019_Boundaries_UK_BGC\Local_Authority_Districts_December_2019_Boundaries_UK_BGC.shp' using driver `ESRI Shapefile'
Simple feature collection with 382 features and 10 fields
geometry type:  MULTIPOLYGON
dimension:      XY
bbox:           xmin: -116.1928 ymin: 5342.7 xmax: 655653.8 ymax: 1220302
epsg (SRID):    NA
proj4string:    +proj=tmerc +lat_0=49 +lon_0=-2 +k=0.9996012717 +x_0=400000 +y_0=-100000 +datum=OSGB36 +units=m +no_defs
tmap_mode("plot")
tmap mode set to plotting
qtm(lad_2019)

LAD boundaries have loaded correctly but they currently cover all of the UK when all we need is London. Because simple feature objects are data frames with a geometry column attached, any operations that we would perform on a normal data frame can also be performed on an object of class sf. Here we will use the dplyr::filter and stringr::str_detect() from the the tidyverse package to only keep LADs whose GSS code starts with “E09”.

4.2.2 Exercise - filter spatial data by variable

  • Inspect lad_2019 using head() or glimpse(), and identify which column holds the GSS codes - it should end in “cd”.
  • Create a new object called london_lad. Use dplyr::filter alongside stringr::str_detect() to only keep observations which have a GSS code starting with “E09”.
  • Plot london_lad to see if the results look correct.

Solution

head(lad_2019)
Simple feature collection with 6 features and 10 fields
geometry type:  MULTIPOLYGON
dimension:      XY
bbox:           xmin: 344666.1 ymin: 378867 xmax: 478441.5 ymax: 537152
epsg (SRID):    NA
proj4string:    +proj=tmerc +lat_0=49 +lon_0=-2 +k=0.9996012717 +x_0=400000 +y_0=-100000 +datum=OSGB36 +units=m +no_defs
  objectid   lad19cd              lad19nm lad19nmw  bng_e  bng_n      long      lat st_areasha st_lengths                       geometry
1        1 E06000001           Hartlepool     <NA> 447160 531474 -1.270189 54.67614   93770350   68481.65 MULTIPOLYGON (((447097 5371...
2        2 E06000002        Middlesbrough     <NA> 451141 516887 -1.210998 54.54468   53858124   42570.87 MULTIPOLYGON (((449862.8 52...
3        3 E06000003 Redcar and Cleveland     <NA> 464361 519597 -1.006086 54.56752  245140395   94686.62 MULTIPOLYGON (((455939.7 52...
4        4 E06000004     Stockton-on-Tees     <NA> 444940 518183 -1.306645 54.55691  204903681  118320.90 MULTIPOLYGON (((444126.1 52...
5        5 E06000005           Darlington     <NA> 428029 515648 -1.568356 54.53534  197485809  105777.87 MULTIPOLYGON (((423475.7 52...
6        6 E06000006               Halton     <NA> 354246 382146 -2.688538 53.33425   79096448   76349.03 MULTIPOLYGON (((358374.7 38...
london_lad <- filter(lad_2019, str_detect(lad19cd, "E09"))
qtm(london_lad)

Finally, for the next step, we only need the outer boundary of London - all the internal LAD boundaries have to be removed and only the outer edges kept. sf has a function exactly for this purpose called st_union(). It only takes one argument, which is the sf object we want to unionise.

4.2.3 Exercise - dissolve boundaries

  • Create a new object called london_boundary using the st_union function.
  • Plot it to check the results.

Solution

london_boundary <- st_union(london_lad)
qtm(london_boundary)

4.3 Spatial subsetting and CRS

In addition to subsetting by value, as we did with the LAD boundaries earlier, we can also subset observations by evaluating their spatial relationship with another data set. We can for example select all LADs which are fully within Wales, every Output Area intersected by a river, or all households outside of city boundaries. There are a number of different spatial relationships which can be tested and used to subset observations.

sf has an inbuilt function called st_filter() which we can use to spatially subset observations. The function takes several arguments:

  • x - sf data frame we want to subset - lfb_sf
  • y - sf object used to evaluate the spatial relationship - london_boundary

Before running any spatial operations on two spatial objects it is always worth checking if their coordinate reference systems (CRS) match. sf will throw an error if that’s not the case. Try it for yourself below.

4.3.1 Exercise - spatial subset part 1

  • Use st_filter() to spatially subset lfb_sf by testing its relationship with london_boundary.

Solution:

lfb_sf <- st_filter(x = lfb_sf, y = london_boundary)

You should have got an error here saying Error in !inherits(x, "sf") : st_crs(x) == st_crs(y) is not TRUE. It means that objects x and y have different CRS. We can see this for ourselves by running the st_crs() function, which returns the coordinate reference system of an object.

4.3.2 Exercise - check CRS

  • Run st_crs() on both and lfb_sf and london_boundary and compare the results.

Solution:

st_crs(lfb_sf)
Coordinate Reference System:
  EPSG: 27700 
  proj4string: "+proj=tmerc +lat_0=49 +lon_0=-2 +k=0.9996012717 +x_0=400000 +y_0=-100000 +ellps=airy +towgs84=446.448,-125.157,542.06,0.15,0.247,0.842,-20.489 +units=m +no_defs"
st_crs(london_boundary)
Coordinate Reference System:
  No EPSG code
  proj4string: "+proj=tmerc +lat_0=49 +lon_0=-2 +k=0.9996012717 +x_0=400000 +y_0=-100000 +datum=OSGB36 +units=m +no_defs"

We can see that london_boundary has not got an EPSG code, and that the proj4string information is different. We can solve this problem by transforming london_boundary’s CRS to match that of lfb_sf, simply by using the correct EPSG code. To do so we will use the st_transform() function which takes two arguments:

  • x - sf object to be transformed
  • crs - EPSG code that we want to transform our data to - BNG is 27700.

4.3.3 Exercise - transform CRS

  • Run st_transform() to transform and overwrite london_boundary. Remember to set the correct CRS.
  • Run st_crs() on lfb_sf and newly transformed london_boundary and compare the results.

Solution:

london_boundary <- st_transform(london_boundary, crs = 27700)

st_crs(lfb_sf)
Coordinate Reference System:
  EPSG: 27700 
  proj4string: "+proj=tmerc +lat_0=49 +lon_0=-2 +k=0.9996012717 +x_0=400000 +y_0=-100000 +ellps=airy +towgs84=446.448,-125.157,542.06,0.15,0.247,0.842,-20.489 +units=m +no_defs"
st_crs(london_boundary)
Coordinate Reference System:
  EPSG: 27700 
  proj4string: "+proj=tmerc +lat_0=49 +lon_0=-2 +k=0.9996012717 +x_0=400000 +y_0=-100000 +ellps=airy +towgs84=446.448,-125.157,542.06,0.15,0.247,0.842,-20.489 +units=m +no_defs"

Now that the CRS are matching we should be able to spatially subset lfb_sf.

4.3.4 Exercise - spatial subset part 2

  • Use st_filter to spatially subset lfb_sf by testing its relationship with london_boundary. Overwrite lfb_sf with the subset data.
  • Plot it to check if the results are correct.

Solution:

lfb_sf <- st_filter(x = lfb_sf, y = london_boundary)
qtm(lfb_sf)

4.4 Spatial and non-spatial joins

Simple features data can be joined to other data sets in two ways. We can either use a traditional, SQL like join, based on a value shared across the data sets or, since we have a geometry column, on the spatial relationship between the data sets. This is known as a spatial join, where variables from one data set are joined to another one only on the basis of their spatial relationship. The most commonly used operation is known as a Point-in-Polygon join where data from a polygon is joined to the points within them.

In sf spatial joins are handled using the st_join() function with arguments:

  • x - sf object to which we are joining data (LHS in SQL)
  • y - sf object whose variables are being joined (RHS in SQL)

We will be joining the Middle Super Output Areas to LFB locations, which will then allow us to group and plot data at MSOA level.

4.4.1 Exercise - spatial joins

  • Read in data/shp/MSOA_2011_london/msoa_2011_ew_bgc.shp as msoa_london - use st_read()
  • Check if msoa_london’s CRS and that of lfb_sf match. Transform msoa_london if necessary.
  • Create a new object called lfb_msoa_sf by running st_join() between lfb_sf and msoa_london
  • Inspect your new object using head() or glimpse() to see what columns have been added.
msoa_london <- st_read("data/shp/MSOA_2011_london/msoa_2011_ew_bgc.shp")
Reading layer `msoa_2011_ew_bgc' from data source `D:\1_projects\Intro_to_geography\intro_to_gis\data\shp\MSOA_2011_london\msoa_2011_ew_bgc.shp' using driver `ESRI Shapefile'
Simple feature collection with 983 features and 2 fields
geometry type:  MULTIPOLYGON
dimension:      XY
bbox:           xmin: 503574.2 ymin: 155850.8 xmax: 561956.7 ymax: 200933.6
epsg (SRID):    NA
proj4string:    +proj=tmerc +lat_0=49 +lon_0=-2 +k=0.9996012717 +x_0=400000 +y_0=-100000 +ellps=airy +units=m +no_defs
st_crs(msoa_london)
Coordinate Reference System:
  No EPSG code
  proj4string: "+proj=tmerc +lat_0=49 +lon_0=-2 +k=0.9996012717 +x_0=400000 +y_0=-100000 +ellps=airy +units=m +no_defs"
st_crs(lfb_sf)
Coordinate Reference System:
  EPSG: 27700 
  proj4string: "+proj=tmerc +lat_0=49 +lon_0=-2 +k=0.9996012717 +x_0=400000 +y_0=-100000 +ellps=airy +towgs84=446.448,-125.157,542.06,0.15,0.247,0.842,-20.489 +units=m +no_defs"
msoa_london <- st_transform(msoa_london, crs = 27700)

lfb_msoa_sf <- st_join(lfb_sf, msoa_london)

head(lfb_msoa_sf)
Simple feature collection with 6 features and 12 fields
geometry type:  POINT
dimension:      XY
bbox:           xmin: 504650 ymin: 164950 xmax: 554650 ymax: 192350
epsg (SRID):    27700
proj4string:    +proj=tmerc +lat_0=49 +lon_0=-2 +k=0.9996012717 +x_0=400000 +y_0=-100000 +ellps=airy +towgs84=446.448,-125.157,542.06,0.15,0.247,0.842,-20.489 +units=m +no_defs

Before we proceed let’s check if all points were succesfully joined. If that’s not the case, some observations will be NA.

4.4.2 Exercise - remove NA joins

  • Use filter() and is.na() on the msoa11cd variable of lfb_msoa_sf to check if any points did not join correctly.
  • Save those to a new object called lfb_msoa_sf_na
  • Create an interactive map of the points and see why they did not join correctly.
  • Remove all NA observations from lfb_msoa_sfand overwrite it - use !is.na to find the correct subset.

Solution

tmap_mode("view")
tmap mode set to interactive viewing
lfb_sf_na <- filter(lfb_msoa_sf, is.na(msoa11cd))
qtm(lfb_sf_na)


lfb_msoa_sf <- filter(lfb_msoa_sf, !is.na(msoa11cd))

Now that msoa11cd is attached to our observations we can create some summary statistics for each MSOA. As mentioned before, we can use standard tidyverse functions on sf objects. Here, we will use dplyr to calculate the total number of incidents and their cost, and then use a non spatial join to attach those results to MSOA boundaries. At this stage we no longer need the geometry column for each LFB incident as a) we’re not performing any spatial operations on our points, and b) the geometry column can slow down/interrupt the dplyr::group_by function which we will be using. To remove the geometry column we can use the st_drop_geometry() function directly in the dplyr pipe.

4.4.3 Exercise - MSOA summary statistics

  • The step requires you to be familiar with dplyr’s more advanced functions. If you are struggling with this step load data/gpkg/msoa_lfb.gpkg as msoa_lfb using st_read.
  • Use st_drop_geometry() on lfb_msoa_sf to remove geometry data.
  • Create summary statistics per MSOA - sum of cost_gbp as total_cost (use na.rm = TRUE), and the total number of incidents as n_cases. You will need to use group_by() and summarise()
  • Create a new column called cost_per_incident using mutate - total_cost divided by n_cases.
  • Join lfb_msoa_stats to msoa_london, using left_join() and create a new object msoa_lfb
lfb_msoa_stats <- lfb_msoa_sf %>% 
                  st_drop_geometry() %>% 
                  group_by(msoa11cd) %>% 
                  summarise(total_cost = sum(cost_gbp, na.rm=TRUE), n_cases = n()) %>% 
                  mutate(cost_per_incident = total_cost/n_cases)
                         

msoa_lfb <- left_join(msoa_london, lfb_msoa_stats)
msoa_lfb
Simple feature collection with 983 features and 5 fields
geometry type:  MULTIPOLYGON
dimension:      XY
bbox:           xmin: 503574.2 ymin: 155850.8 xmax: 561956.7 ymax: 200933.6
epsg (SRID):    27700
proj4string:    +proj=tmerc +lat_0=49 +lon_0=-2 +k=0.9996012717 +x_0=400000 +y_0=-100000 +ellps=airy +towgs84=446.448,-125.157,542.06,0.15,0.247,0.842,-20.489 +units=m +no_defs
First 10 features:
    msoa11cd                 msoa11nm total_cost n_cases cost_per_incident                       geometry
1  E02000001       City of London 001       3750      12          312.5000 MULTIPOLYGON (((531667.6 18...
2  E02000002 Barking and Dagenham 001       2221       7          317.2857 MULTIPOLYGON (((548881.6 19...
3  E02000003 Barking and Dagenham 002       4779      11          434.4545 MULTIPOLYGON (((549102.4 18...
4  E02000004 Barking and Dagenham 003       2917       7          416.7143 MULTIPOLYGON (((551550 1873...
5  E02000005 Barking and Dagenham 004       1113       4          278.2500 MULTIPOLYGON (((549099.6 18...
6  E02000007 Barking and Dagenham 006       2756       9          306.2222 MULTIPOLYGON (((549819.9 18...
7  E02000008 Barking and Dagenham 007       3205      10          320.5000 MULTIPOLYGON (((548171.4 18...
8  E02000009 Barking and Dagenham 008       2067       7          295.2857 MULTIPOLYGON (((546855 1863...
9  E02000010 Barking and Dagenham 009        859       3          286.3333 MULTIPOLYGON (((549618.8 18...
10 E02000011 Barking and Dagenham 010        550       2          275.0000 MULTIPOLYGON (((550244.1 18...
msoa_lfb <- st_read("data/gpkg/msoa_lfb.gpkg") 

At this stage it is a good idea to save our data. We can do this using the st_write() function. It needs an sf object and the path and name of the output.

4.4.4 Exercise - save data to gpkg

  • Copy and execute the following code to save your data: st_write(msoa_lfb,"output/msoa_lfb.gpkg)

5 Making better maps

Now that we have processed our data we can start mapping it. So far we have only used the qtm() function from the tmap package. This creates a default map and is great when all we want to do is quickly visualise our data. The full range of tmap functions gives us control over all elements of the final plot and allows us to create high quality maps.

tmap_mode("plot")

tm_shape(msoa_lfb) + 
  tm_polygons(col = "total_cost", border.col = "#4a4949", lwd = 0.05, title = "Total cost (£)", palette = "Blues", contrast = 1, legend.hist = TRUE,
        labels = c("0 - 2,000", ">2,000 - 4,000", ">4,000 - 6,000", ">6,000 - 8,000", ">8,000 - 10,000", 
                   ">10,000 - 12,000", ">12,000 - 14,000")) +
  tm_scale_bar(position = c(0,0), text.size = 0.7) +
  tm_layout(main.title = "Cost of animal related incidents per MSOA, between 2009 and 2020",  main.title.position =  c(0,0), main.title.size = 1, main.title.fontface = "bold", frame = FALSE, legend.position  = c(0.08,0.18),
            inner.margins = c(0.1,0.05,0.1,0.02), legend.outside = TRUE, legend.title.size  = 1, legend.text.size =  0.7, title.snap.to.legend = FALSE) +
  tm_shape(london_boundary) + tm_borders(col = "black", lwd = 0.25)

tmap follows similar principles to ggplot2, where we first specify the data source - tm_shape, then the aesthetics of the plot - tm_polygons, tm_dots, etc., and then we make any finaly adjustments - tm_layout. All functions need to be connected using the + symbol.

  • tm_shape() - sf object which you want to plot
  • tm_fill(), tm_borders(), tm_polygons(), tm_dots() - types of output
  • tm_layout() - controls layout of the map, titles, labels, etc.

tmap syntax: tm_shape(sf_object) + tm_borders(col = either "colour" or name of column which we want to plot) + tm_layout(main.title = "title of your map")

5.0.1 Guided exercise - mapping

Start by specifying which sf object is being mapped in tm_shape() and what column holds the values to be visualised. We will also change the legend’s title.

tm_shape(msoa_lfb) + 
  tm_polygons(col = "cost_per_incident", title = "Cost per Incident (£)")

Now let’s add london_boundary to have a thicker line around London.

tm_shape(msoa_lfb) + 
  tm_polygons(col = "cost_per_incident", title = "Cost per Incident (£)") + 
  tm_shape(london_boundary) + tm_borders(col = "black")

Next we will add a scale bar and position it in the bottom left corner.

tm_shape(msoa_lfb) + 
  tm_polygons(col = "cost_per_incident", title = "Cost per Incident (£)") + 
  tm_shape(london_boundary) + tm_borders(col = "black") +
  tm_scale_bar(position = c(0,0))

We can now remove the black frame from the map and add a title to our map.

tm_shape(msoa_lfb) + 
  tm_polygons(col = "cost_per_incident", title = "Cost per Incident (£)") + 
  tm_shape(london_boundary) + tm_borders(col = "black") +
  tm_scale_bar(position = c(0,0)) +
   tm_layout(title = "Average cost of animal related incidents between 2009 and 2020",  
            frame = FALSE)

All of the map elements are now visible but they’re not in the right place. We can solve this by increasing the margins around our map. This will allow the title and the legend to move outwards.

tm_shape(msoa_lfb) + 
  tm_polygons(col = "cost_per_incident", title = "Cost per Incident (£)") + 
  tm_shape(london_boundary) + tm_borders(col = "black") +
  tm_scale_bar(position = c(0,0)) +
   tm_layout(title = "Average cost of animal related incidents between 2009 and 2020",  
            frame = FALSE, inner.margins = c(0.1,0.1,0.1,0.15))

We can also manually change the legend labels to ensure there are no overlapping values.

tm_shape(msoa_lfb) + 
  tm_polygons(col = "cost_per_incident", title = "Cost per Incident (£)",
              labels = c("0 - 200", ">200 - 400", ">400 - 600", ">600 - 800", ">800 - 1,000", 
                   ">1,000 - 1,200")) + 
  tm_shape(london_boundary) + tm_borders(col = "black") +
  tm_scale_bar(position = c(0,0)) +
   tm_layout(title = "Average cost of animal related incidents between 2009 and 2020",  
            frame = FALSE, inner.margins = c(0.1,0.1,0.1,0.15))

Finally let’s change the colour of our map and increase the contrast. Choose a colour from R Colours.

tm_shape(msoa_lfb) + 
  tm_polygons(col = "cost_per_incident", title = "Cost per Incident (£)",
              labels = c("0 - 200", ">200 - 400", ">400 - 600", ">600 - 800", ">800 - 1,000", 
                   ">1,000 - 1,200"), palette = "Blues", contrast = 1) + 
  tm_shape(london_boundary) + tm_borders(col = "black") +
  tm_scale_bar(position = c(0,0)) +
   tm_layout(title = "Average cost of animal related incidents between 2009 and 2020",  
            frame = FALSE, inner.margins = c(0.1,0.1,0.1,0.15))

Finally, save your map as an R object and export it.

average_cost <- tm_shape(msoa_lfb) + 
  tm_polygons(col = "cost_per_incident", title = "Cost per Incident (£)", palette = "Blues", contrast = 1) + 
  tm_shape(london_boundary) + tm_borders(col = "black") +
  tm_scale_bar(position = c(0,0)) +
   tm_layout(title = "Average cost of animal related incidents between 2009 and 2020",  
            frame = FALSE, inner.margins = c(0.1,0.1,0.1,0.15))
tmap_save(average_cost, "output/maps/average_cost_msoa.png", width = 8, height = 5)

You can also view your choropleth as an interactive map. It helps to add an alpha argument to change your map’s transparency.

tmap_mode("view")
tmap mode set to interactive viewing
tm_shape(msoa_lfb) + 
  tm_polygons(col = "cost_per_incident", title = "Cost per Incident (£)", palette = "Blues", contrast = 1, alpha = 0.5) + 
  tm_shape(london_boundary) + tm_borders(col = "black") 
LS0tDQp0aXRsZTogIkludHJvZHVjdGlvbiB0byBHSVMgaW4gUiINCmF1dGhvcjogIlJvYmVydCBLYWxldGEiDQpkYXRlOiAiRmVicnVhcnkgMjAyMCINCm91dHB1dDoNCiAgaHRtbF9ub3RlYm9vazoNCiAgICBudW1iZXJfc2VjdGlvbnM6IHllcw0KICAgIHRoZW1lOiBmbGF0bHkNCiAgICB0b2M6IHllcw0KICAgIHRvY19kZXB0aDogMw0KICAgIHRvY19mbG9hdDogeWVzDQojICBodG1sX2RvY3VtZW50Og0KIyAgICBkZl9wcmludDogcGFnZWQNCiMgICAgdG9jOiB5ZXMNCiMgICAgdG9jX2RlcHRoOiAnMycNCi0tLQ0KDQpgYGB7ciwgZWNobz1GQUxTRX0NCmh0bWx0b29sczo6aW1nKHNyYyA9IGtuaXRyOjppbWFnZV91cmkoIkQ6LzNfT05TX0RPQ1VNRU5UUy9nZW9zcGF0aWFsX2xvZ29zL3NtYWxsIGdlbyBpY29uLnBuZyIpLCANCiAgICAgICAgICAgICAgIGFsdCA9ICdsb2dvJywgDQogICAgICAgICAgICAgICBzdHlsZSA9ICdwb3NpdGlvbjphYnNvbHV0ZTsgdG9wOjUwcHg7IHJpZ2h0OjA7IHBhZGRpbmc6MTBweDsnKQ0KYGBgDQoNCmBgYHtyLCBlY2hvPUZBTFNFfQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGZpZy5hbGlnbj0iY2VudGVyIikNCmBgYA0KDQpgYGB7ciBsaWJyYXJpZXMsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGVjaG89RkFMU0V9DQpsaWJyYXJ5KCJzZiIpDQpsaWJyYXJ5KCJ0aWR5dmVyc2UiKQ0KbGlicmFyeSgidG1hcCIpDQpsaWJyYXJ5KCJrbml0ciIpDQpgYGANCg0KIyBTZXQgdXAgUg0KDQoqIEdvIHRvIFtHaXRodWJdKGh0dHBzOi8vZ2l0aHViLmNvbS9PTlNHZW9zcGF0aWFsL0ludHJvZHVjdGlvbl9HSVNfUikgYW5kIGRvd25sb2FkIHRoZSByZXBvc2l0b3J5IGFzIGEgemlwIGZpbGUuIA0KKiBNb3ZlIGl0IHRvIHlvdXIgbG9jYWwgZHJpdmUuDQoqIEluIFJTdHVkaW8gZ28gdG8gRmlsZSAtPiBOZXcgUHJvamVjdCAtPiBFeGlzdGluZyBEaXJlY3RvcnkgLT4gRG93bmxvYWRlZCBHaXRodWIgcmVwb3NpdG9yeS4NCiogUnVuIGBnZXR3ZCgpYCBpbiB5b3VyIFIgY29uc29sZSBhbmQgY2hlY2sgaWYgdGhlIHBhdGggZW5kcyBpbiAiSW50cm9fdG9fUl9naXMiLg0KKiBPcGVuIGEgbmV3IHNjcmlwdDogRmlsZSAtPiBOZXcgRmlsZSAtPiBSIFNjcmlwdCAtIHRoaXMgaXMgd2hlcmUgeW91IHdpbGwgd3JpdGUgYWxsIG9mIHRvZGF5J3MgY29kZS4NCiogV29ya2luZyBpbiBhbiBSIHByb2plY3QgbWVhbnMgdGhhdCBvdXIgZmlsZSBwYXRocyBhcmUgYWxsIHJlbGF0aXZlIHRvIHRoZSAiSW50cm9fdG9fUl9HSVMiIGZvbGRlci4NCg0KIyMgUXVpY2sgUiBiYXNpY3MNCg0KKiBSIGlzIGNhc2Ugc2Vuc2l0aXZlIC0gYHJlYWRfY3N2YCBpcyBub3QgdGhlIHNhbWUgYXMgYHJlYWRfQ1NWYC4NCiogTmV3IG9iamVjdHMgYXJlIGNyZWF0ZWQgdXNpbmcgdGhlIGA8LWAgbm90YXRpb24sIGUuZyBgbmV3X29iamVjdCA8LSAyICogNWAgLg0KKiBUbyBvdmVyd3JpdGUgYW4gb2JqZWN0IHVzZSBgPC1gIGFuZCBpdHMgY3VycmVudCBuYW1lLCBlLmcuIGBjdXJyZW50X29iamVjdCA8LSBzdF91bmlvbihjdXJyZW50X29iamVjdClgLg0KKiBGdW5jdGlvbidzIGFyZ3VtZW50cyBoYXZlIHRvIGJlIGluIGAoKWAgYW5kIHRoZXkncmUgZGVmaW5lZCB3aXRoIGEgYD1gLCBlLmcuIGBzdF90cmFuc2Zvcm0oeCA9IGxmYl9zZiwgY3JzID0gMjc3MDApYA0KKiBUbyBzZWUgZnVuY3Rpb24ncyBkb2N1bWVudGF0aW9uIHByZWNlZWQgaXRzIG5hbWUgd2l0aCBhIGA/YCwgZS5nIGA/c3RfYXNfc2ZgLg0KKiBXcml0ZSBpbiB0aGUgY29kZSBlZGl0b3IgKHNjcmlwdCkgYW5kIGV4ZWN1dGUgeW91ciBjb2RlIGxpbmUgYnkgbGluZS4NCiogVXNlIGBDdHJsICsgRW50ZXJgIHRvIGV4ZWN1dGUgdGhlIGN1cnJlbnQgbGluZSBvZiBjb2RlLg0KDQojIyBJbnN0YWxsICYgbG9hZCBSIGxpYnJhcmllcw0KDQpJZiB5b3UgaGF2ZSBub3QgaW5zdGFsbGVkIHRoZSBuZWNlc3NhcnkgcGFja2FnZXMgcnVuIGBpbnN0YWxsLnBhY2thZ2VzKClgIGFuZCB0aGVuIGxvYWQgdGhlbSB1c2luZyB0aGUgYGxpYnJhcnkoKWAgZnVuY3Rpb24uDQpgYGB7ciwgZXZhbD1GQUxTRX0NCmluc3RhbGwucGFja2FnZXMoInNmIiwgZGVwZW5kZW5jaWVzID0gVFJVRSwgdHlwZSA9ICJ3aW4uYmluYXJ5IikNCmluc3RhbGwucGFja2FnZXMoInRtYXAiLCBkZXBlbmRlbmNpZXMgPSBUUlVFLCB0eXBlID0gIndpbi5iaW5hcnkiKQ0KaW5zdGFsbC5wYWNrYWdlcygidGlkeXZlcnNlIiwgZGVwZW5kZW5jaWVzID0gVFJVRSwgdHlwZSA9ICJ3aW4uYmluYXJ5IikNCg0KbGlicmFyeShzZikgDQpsaWJyYXJ5KHRtYXApDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmBgYA0KDQoNCiMgQWltcw0KDQoqKkJ5IHRoZSBlbmQgb2YgdG9kYXkgeW91IHdpbGw6ICoqDQogDQoqIFVuZGVyc3RhbmQgd2hhdCBzcGF0aWFsIGRhdGEgYW5kIEdJUyBhcmUuDQoqIEtub3cgaG93IHRvIHVzZSB0aGUgT3BlbiBHZW9ncmFwaHkgUG9ydGFsLg0KKiBCZSBhd2FyZSBvZiBtYXAgcHJvamVjdGlvbnMgYW5kIENvb3JkaW5hdGUgUmVmZXJlbmNlIFN5c3RlbXMgKENSUykgYW5kIGJlIGFibGUgdG8gbW9kaWZ5IHRoZW0uDQoqIEJlIGFibGUgdG8gbG9hZCBzcGF0aWFsIGRhdGEgaW50byBSIHVzaW5nIHRoZSBgc2ZgIGxpYnJhcnkuDQoqIEJlIGZhbWlsaWFyIHdpdGggdXNpbmcgR1NTIGNvZGVzIHRvIGpvaW4gc3RhdGlzdGljcyB0byBnZW9ncmFwaGllcy4NCiogVW5kZXJzdGFuZCBob3cgc3BhdGlhbCBvYmplY3RzIGNhbiBiZSBtYW5pcHVsYXRlZCB1c2luZyBSJ3MgYHRpZHl2ZXJzZWAuDQoqIFVuZGVyc3RhbmQgaG93IHRvIHVzZSBzcGF0aWFsIGpvaW5zLiANCiogS25vdyBob3cgdG8gbWFrZSBzdGF0aWMgYW5kIGludGVyYWN0aXZlIG1hcHMgaW4gYHRtYXBgLg0KKiBCZSBhYmxlIHRvIGV4cG9ydCB5b3VyIG1hcHMgYW5kIHNoYXBlZmlsZXMuDQoNCg0KIyBHSVMgJiBTcGF0aWFsIERhdGENCg0KKipHSVMqKiBzdGFuZHMgZm9yIEdlb2dyYXBoaWMgSW5mb3JtYXRpb24gU3lzdGVtL1NjaWVuY2UgLSB0aGUgU3lzdGVtIHBhcnQgcmVmZXJzIHRvIHRoZSBzb2Z0d2FyZSB1c2VkIGZvciBjYXB0dXJpbmcsIHN0b3JpbmcsIGFuZCBtYW5pcHVsYXRpbmcgc3BhdGlhbCBkYXRhLCB3aGlsZSB0aGUgU2NpZW5jZSBkZWZpbml0aW9uIGlzIGNvbmNlcm5lZCB3aXRoIHNjaWVudGlmaWMgcHJpbmNpcGxlcyBiZWhpbmQgc3BhdGlhbCBhbmFseXNpcywgYW5kIGRldmVsb3BpbmcgbmV3IG1ldGhvZHMgYW5kIGFwcHJvYWNoZXMgdG8gZXh0cmFjdCBpbnNpZ2h0IGZyb20gZ2VvZ3JhcGhpYyBkYXRhLiANCg0KPGNlbnRlcj4NCiFbXShkYXRhL2ltZy9naXNfbG9nb3MucG5nKQ0KPC9jZW50ZXI+DQoNCioqU3BhdGlhbCBEYXRhKiogLSBhbnkgZGF0YSBzZXQgd2hpY2ggaGFzLCBvciBoYXMgdGhlIHBvdGVudGlhbCB0byBoYXZlLCBsb2NhdGlvbiBhdHRhY2hlZCB0byBpdC4gVGhpcyBpbmNsdWRlcywgYnV0IGlzIG5vdCBsaW1pdGVkIHRvLCBjb29yZGluYXRlcywgYWRkcmVzc2VzLCBhbmQgZ2VvZ3JhcGh5IGNvZGVzLg0KDQoqKlNwYXRpYWwgZGF0YSB0eXBlczoqKg0KDQoqIFZlY3RvciAtIHBvaW50cywgbGluZXMsIGFuZCBwb2x5Z29ucyB1c2VkIHRvIHJlcHJlc2VudCBwaHlzaWNhbCBhbmQgYWRtaW5pc3RyYXRpdmUgZmVhdHVyZXMuIFVzZWQgZm9yIGRpc3BsYXlpbmcgZGF0YSB3aXRoIHdlbGwgZGVmaW5lZCBleHRlbnQuDQoNCjxjZW50ZXI+DQohW10oZGF0YS9pbWcvdmVjdG9yX2V4YW1wbGUucG5nKQ0KPC9jZW50ZXI+DQoNCiogUmFzdGVyIC0gcGl4ZWwgYmFzZWQgZGF0YSwgb2Z0ZW4gZGVyaXZlZCBmcm9tIHNhdGVsbGl0ZSBpbWFnZXJ5LiBVc2VkIGZvciBkaXNwbGF5aW5nIGNvbnRpbnVvdXMgb3IgZnV6enkgdmFyaWFibGVzLiANCg0KPGNlbnRlcj4NCiFbXShkYXRhL2ltZy9yYXN0ZXJfZXhhbXBsZV9zbWFsbC5wbmcpDQo8L2NlbnRlcj4NCg0KDQoNCg0KKipDb21tb24gc3BhdGlhbCBkYXRhIGZvcm1hdHM6KioNCg0KKiBTaGFwZWZpbGUgKC5zaHApIA0KKiBHZW9QYWNrYWdlICguZ3BrZykgLyBHZW9kYXRhYmFzZSAoLmdkYikNCiogR2VvSlNPTiAvIFRvcG9KU09ODQoqIFdlbGwta25vd24tdGV4dA0KKiBHZW9UaWZmDQoNCiMjIE1hcCBQcm9qZWN0aW9uIGFuZCBDb29yZGluYXRlIFJlZmVyZW5jZSBTeXN0ZW0NCg0KKkluIGNhcnRvZ3JhcGh5LCBhIG1hcCBwcm9qZWN0aW9uIGlzIGEgd2F5IHRvIGZsYXR0ZW4gYSBnbG9iZSdzIHN1cmZhY2UgaW50byBhIHBsYW5lIGluIG9yZGVyIHRvIG1ha2UgYSBtYXAuIFRoaXMgcmVxdWlyZXMgYSBzeXN0ZW1hdGljIHRyYW5zZm9ybWF0aW9uIG9mIHRoZSBsYXRpdHVkZXMgYW5kIGxvbmdpdHVkZXMgb2YgbG9jYXRpb25zIGZyb20gdGhlIHN1cmZhY2Ugb2YgdGhlIGdsb2JlIGludG8gbG9jYXRpb25zIG9uIGEgcGxhbmUuIEFsbCBwcm9qZWN0aW9ucyBvZiBhIHNwaGVyZSBvbiBhIHBsYW5lIG5lY2Vzc2FyaWx5IGRpc3RvcnQgdGhlIHN1cmZhY2UgaW4gc29tZSB3YXkgYW5kIHRvIHNvbWUgZXh0ZW50LiBEZXBlbmRpbmcgb24gdGhlIHB1cnBvc2Ugb2YgdGhlIG1hcCwgc29tZSBkaXN0b3J0aW9ucyBhcmUgYWNjZXB0YWJsZSBhbmQgb3RoZXJzIGFyZSBub3Q7IHRoZXJlZm9yZSwgZGlmZmVyZW50IG1hcCBwcm9qZWN0aW9ucyBleGlzdCBpbiBvcmRlciB0byBwcmVzZXJ2ZSBzb21lIHByb3BlcnRpZXMgb2YgdGhlIHNwaGVyZS1saWtlIGJvZHkgYXQgdGhlIGV4cGVuc2Ugb2Ygb3RoZXIgcHJvcGVydGllcy4gRXZlcnkgZGlzdGluY3QgbWFwIHByb2plY3Rpb24gZGlzdG9ydHMgaW4gYSBkaXN0aW5jdCB3YXksIGJ5IGRlZmluaXRpb24uKg0KDQo8Zm9udCBzaXplPSIyIj5Tb3VyY2U6IFdpa2lwZWRpYSA8L2ZvbnQ+DQoNCjxjZW50ZXI+DQohW10oZGF0YS9pbWcvbWFwX3Byb2plY3Rpb24ucG5nKQ0KPC9jZW50ZXI+DQo8Zm9udCBzaXplPSIyIj5bU291cmNlXShodHRwczovL3d3dy5yZXNlYXJjaGdhdGUubmV0L3Byb2ZpbGUvQm9qYW5fU2F2cmljMi9wdWJsaWNhdGlvbi8yOTgzNTQyNzgvZmlndXJlL2ZpZzEvQVM6NjE0MzU0OTUzMTk5NjMxQDE1MjM0ODUwNDM0NzMvVGhlLW5pbmUtc21hbGwtLS1zY2FsZS1tYXAtcHJvamVjdGlvbnMtdXNlZC1pbi10aGUtcGFpcmVkLWNvbXBhcmlzb24tdGVzdC1hcnJhbmdlZC1ieS5wbmcpPC9mb250Pg0KDQoqKkFsbCB5b3UgbmVlZCB0byBrbm93IGZvciB0b2RheSBpczoqKg0KDQoqIFdoZW4gd29ya2luZyB3aXRoIEdCIGRhdGEgdXNlIHRoZSAqKkJyaXRpc2ggTmF0aW9uYWwgR3JpZCAoQk5HKSoqLg0KKiBCTkcgdXNlcyAqKkVhc3RpbmdzKiogYW5kICoqTm9ydGhpbmdzKiogd2hpY2ggYXJlIGdpdmVuIGFzIG1ldHJlcywgb2Zmc2V0IGZyb20gdGhlIG9yaWdpbiBwb2ludC4NCiogQk5HJ3MgRVBTRyBjb2RlIGlzICoqMjc3MDAqKi4NCg0KDQoNCjxjZW50ZXI+DQohW10oZGF0YS9pbWcvYm5nLnBuZykNCg0KPC9jZW50ZXI+DQoNCg0KIyMgR0lTIGFuZCBSDQoNClIgaXMgY29tbW9ubHkgdXNlZCBmb3Igc3RhdGlzdGljYWwgYW5hbHlzaXMgYW5kIHByb2dyYW1taW5nLCBob3dldmVyIGl0IGFsc28gaGFzIGEgd2hvbGUgcmFuZ2Ugb2YgR0lTIHRvb2xzLiBUaGVyZSBpcyBhIGxvbmcgaGlzdG9yeSBvZiBnZW9zcGF0aWFsIGxpYnJhcmllcyBiZWluZyBkZXZlbG9wZWQgZm9yIFIgYW5kIGFuIGFtYXppbmcgY29tbXVuaXR5IG9mIHJlc2VhcmNoZXJzIGFuZCBwcm9ncmFtbWVycyBhcm91bmQgaXQuIEluIHRoZSBsYXN0IGZldyB5ZWFycywgd29ya2luZyB3aXRoIHNwYXRpYWwgZGF0YSBiZWNhbWUgbXVjaCBlYXNpZXIgaW4gUiwgd2l0aCB0aGUgZGV2ZWxvcG1lbnQgb2YgdGhlIGBzZmAgcGFja2FnZS4gYHNmYCBrZWVwcyBhbGwgc3BhdGlhbCBpbmZvcm1hdGlvbiBmb3IgZWFjaCBvYnNlcnZhdGlvbiBpbiBhIGdlb21ldHJ5IGNvbHVtbiB3aGljaCBtZWFucyB0aGF0IHdlIGNhbiB0cmVhdCBpdCBsaWtlIGEgbm9ybWFsIGRhdGEgZnJhbWUgYnV0IGFsc28gcGVyZm9ybSBhbGwgdHlwZXMgb2Ygc3BhdGlhbCBvcGVyYXRpb25zIG9uIHRoZSBkYXRhLiAgDQoNCmBgYHtyIGVjaG89RkFMU0V9DQp3ZF8yMDE5X2JnYyA8LSBzdF9yZWFkKCJkYXRhL3NocC9XYXJkc19EZWNlbWJlcl8yMDE5X0JvdW5kYXJpZXNfRVdfQkdDL1dhcmRzX0RlY2VtYmVyXzIwMTlfQm91bmRhcmllc19FV19CR0Muc2hwIiwgcXVpZXQgPSBUUlVFKSAlPiUgDQogIHNlbGVjdCh3ZDE5Y2QsIHdkMTlubSkNCmhlYWQod2RfMjAxOV9iZ2MpDQpgYGANCg0KDQojIyBTb3VyY2VzIG9mIHNwYXRpYWwgZGF0YQ0KDQpXaGVuIHdvcmtpbmcgd2l0aCBtb3N0IHR5cGVzIG9mIGRhdGEgeW91IHdpbGwgbW9zdCBjb21tb25seSBuZWVkIHRvIGxpbmsgdGhlbSB0byBvZmZpY2lhbCBhZG1pbmlzdHJhdGl2ZSBhbmQgY2Vuc3VzIGJvdW5kYXJpZXMsIGFzIHdlbGwgYXMgbW9yZSBnZW5lcmFsIGdlb2dyYXBoaWMgZGF0YS4gTW9zdCBuYXRpb25hbCBhbmQgbG9jYWwgZ292ZXJubWVudHMgd2lsbCBoYXZlIGEgZ2VvZ3JhcGh5IHBvcnRhbCB3aGVyZSBib3VuZGFyaWVzIGNhbiBiZSBkb3dubG9hZGVkLiBBdCBPTlMgYm91bmRhcmllcywgbG9va3VwcywgYW5kIGRvY3VtZW50YXRpb24gY2FuIGJlIGRvd25sb2FkZWQgZnJvbSB0aGUgW09wZW4gR2VvZ3JhcGh5IFBvcnRhbF0oaHR0cDovL2dlb3BvcnRhbC5zdGF0aXN0aWNzLmdvdi51ay8pLCB3aGlsZSBvdGhlciBnZW9ncmFwaGljIGZlYXR1ZXMgY2FuIGJlIGFjY2Vzc2VkIGZyb20gdGhlIFtPcmRuYW5jZSBTdXJ2ZXldKGh0dHA6Ly9vcmRuYW5jZXN1cnZleS5jby51aykuICANCg0KPGNlbnRlcj4NCiFbXShkYXRhL2ltZy9nZW9fcG9ydGFsLnBuZykNCjwvY2VudGVyPiAgDQoNCg0KDQpCb3VuZGFyaWVzIG1hbmFnZWQgYnkgdGhlIE9OUyBjb21lIGluIHNldmVyYWwgcmVzb2x1dGlvbnMgKG1vcmUgZ2VuZXJhbGlzZWQgYm91bmRhcmllcyBzYWNyaWZpY2UgYWNjdXJhY3kgYW5kIHByZWNpc2lvbiBmb3IgZmlsZSBzaXplIGFuZCBwcm9jZXNzaW5nIHNwZWVkcyk6DQoNCiogRiAtIEZ1bGwgcmVzb2x1dGlvbg0KKiBHIC0gR2VuZXJhbGlzZWQNCiogVSAtIFVsdHJhIGdlbmVyYWxpc2VkDQoNCmFuZCBleHRlbnRzIChleHRlbnQgZGV0ZXJtaW5lcyB3aGVyZSB0aGUgbGFuZC93YXRlciBib3VuZGFyeSBpcyAtIGV4dGVudCBvZiB0aGUgcmVhbG0gaW5jbHVkZXMgYXJlYXMgb2Ygd2F0ZXIpOg0KDQoNCiogQyAtIENsaXBwZWQgdG8gY29hc3RsaW5lDQoqIEUgLSBFeHRlbnQgb2YgdGhlIHJlYWxtDQoNCg0KPGNlbnRlcj4NCiFbXShkYXRhL2ltZy9iZ2Nfc21hbGwucG5nKQ0KPC9jZW50ZXI+DQoNCg0KIyMjIEV4ZXJjaXNlIC0gT3BlbiBHZW9ncmFwaHkgUG9ydGFsDQoNCiogR28gdG8gdGhlIFtPcGVuIEdlb2dyYXBoeSBQb3J0YWxdKGh0dHA6Ly9nZW9wb3J0YWwuc3RhdGlzdGljcy5nb3YudWsvKSBhbmQgZG93bmxvYWQgdGhlIDIwMTkgTG9jYWwgQXV0aG9yaXR5IERpc3RyaWN0IEJHQyBib3VuZGFyaWVzLiBTYXZlIHRoZW0gdG8gYGRhdGEvc2hwYCBpbiB5b3VyIHByb2plY3Qgc3RydWN0dXJlIGFuZCB1bnppcC4gDQoqIE9wZW4gdGhlIGZvbGRlciBhbmQgc2VlIGhvdyBtYW55IGRpZmZlcmVudCBmaWxlcyB0aGVyZSBhcmUuDQoNCg0KIyBMb25kb24gRmlyZSBCcmlnYWRlIEFuaW1hbCBSZXNjdWUgRGF0YQ0KDQpUcm91Z2hvdXQgdGhpcyB0dXRvcmlhbCB3ZSB3aWxsIGJlIHVzaW5nIGRhdGEgZnJvbSB0aGUgTG9uZG9uIEZpcmUgQnJpZ2FkZSAtIFtMRkIgQW5pbWFsIFJlc2N1ZSBEYXRhXShodHRwczovL2RhdGEubG9uZG9uLmdvdi51ay9kYXRhc2V0L2FuaW1hbC1yZXNjdWUtaW5jaWRlbnRzLWF0dGVuZGVkLWJ5LWxmYikuIEl0IGNvdmVycyBhbGwgaW5jaWRlbnRzIGJldHdlZW4gMjAwOSBhbmQgMjAyMCB3aGljaCBpbmNsdWRlZCBhc3Npc3RhbmNlIHRvIGFuaW1hbHMgdGhhdCBtYXkgYmUgdHJhcHBlZCBvciBpbiBkaXN0cmVzcy4gVGhlIGRhdGEgaXMgdXBkYXRlZCBtb250aGx5IGFuZCBpbmNsdWRlcyBhIHJhbmdlIG9mIHZhcmlhYmxlcyBmb3IgZWFjaCBpbmNpZGVudCBpbmNsdWRpbmcgc29tZSBsb2NhdGlvbiBpbmZvcm1hdGlvbiAocG9zdGNvZGUsIGJvcm91Z2gsIHdhcmQpLHRoZSBkYXRlL3RpbWUgb2YgdGhlIGluY2lkZW50cywgY29zdCwgYW5kIHR5cGUgb2YgYW5pbWFsIGluIHRyb3VibGUuIA0KDQpXZSB3YW50IHRvIHZpc3VhbGlzZSwgYW5kIGJldHRlciB1bmRlcnN0YW5kIGhvdyBtdWNoIG1vbmV5IGhhcyBiZWVuIHNwZW50IG9uIGFuaW1hbCByZWxhdGVkIGluY2lkZW50cyBiZXR3ZWVuIDIwMDkgYW5kIDIwMjAsIGFuZCB3aGF0IHRoZSBkaXN0cmlidXRpb24gaXMgYXQgdGhlIE1TT0EgbGV2ZWwgb2YgZ2VvZ3JhcGh5LiBUbyBhY2hpZXZlIHRoaXMgd2Ugd2lsbCBoYXZlIHRvIGltcG9ydCBzcGF0aWFsIGRhdGEsIG1hbmlwdWxhdGUgaXQgYW5kIGNyZWF0ZSBzdW1tYXJ5IHN0YXRpc3RpY3MsIGFuZCB0aGVuIHBsb3QgaXQuDQoNCiMjIExvYWRpbmcgc3BhdGlhbCBhbmQgbm9uLXNwYXRpYWwgZGF0YQ0KDQpMRkIgZGF0YSBoYXMgYmVlbiB0aWRpZWQgdXAgYW5kIHNhdmVkIGFzIGEgQ29tbWEgU2VwYXJhdGVkIFZhbHVlIGZpbGUgKC5jc3YpLiBXZSBjYW4gdXNlIGByZWFkX2NzdmAgdG8gb3BlbiBpdCBpbiBSLg0KDQojIyMgRXhlcmNpc2UgLSBvcGVuIExGQiBkYXRhDQoNCiogQ3JlYXRlIGEgbmV3IG9iamVjdCBjYWxsZWQgYGxmYmAgYnkgdXNpbmcgYHJlYWRfY3N2KClgLiBMb2FkIGRhdGEgbG9jYXRlZCBpbiAiZGF0YS9jc3YvbGZiXzIwMDlfMjAyMC5jc3YiLg0KKiBVc2UgYGdsaW1wc2UoKWAgb3IgYGhlYWQoKWAgdG8gdmlldyBgbGZiYCBzdHJ1Y3R1cmUuDQoNCioqU29sdXRpb24qKg0KYGBge3IgbWVzc2FnZT1GQUxTRX0NCmxmYiA8LSByZWFkX2NzdigiZGF0YS9jc3YvbGZiXzIwMDlfMjAyMC5jc3YiKQ0KaGVhZChsZmIpDQpgYGANCg0KYGxmYmAgaXMgY3VycmVudGx5IGp1c3QgYSBkYXRhIGZyYW1lLiBJdCBoYXMgbm90IGdvdCBhbiBleHBsaWNpdCBnZW9tZXRyeSBjb2x1bW4gd2hpY2ggbGlua3Mgb2JzZXJ2YXRpb25zIHRvIHRoZWlyIGdlb2dyYXBoaWMgbG9jYXRpb24uIEl0IGRvZXMgaG93ZXZlciBjb250YWluIHNldmVyYWwgY29sdW1ucyB3aGljaCBjYW4gYmUgdXNlZCB0byBjb252ZXJ0IGl0IGludG8gYSBzcGF0aWFsIGRhdGEgZm9ybWF0LiAgIA0KDQoqKldhcmRfY29kZSoqIGNvbHVtbiByZWZlcmVuY2VzIHRoZSBHU1MgY29kZXMgb2Ygd2FyZHMgd2l0aGluIHdoaWNoIHRoZSBvYnNlcnZhdGlvbnMgZmFsbC4gR1NTIGNvZGVzIGNhbiBiZSB1c2VkIHRvIGpvaW4gYGxmYmAgZGF0YSB0byBib3VuZGFyaWVzIGZyb20gdGhlIE9wZW4gR2VvZ3JhcGh5IFBvcnRhbC4gT25lIGlzc3VlIHdpdGggdGhpcyBwYXJ0aWN1bGFyIGNvbHVtbiBpcyB0aGF0IGl0IGRvZXMgbm90IGluZGljYXRlIHRoZSBjdXJyZW5jeSBvZiBHU1MgY29kZXMuIFdhcmRzIGFyZSBzdWJqZWN0IHRvIGZyZXF1ZW50IGNoYW5nZSwgYW5kIGFzIHN1Y2ggaXQgaXMgYmVzdCBwcmFjdGljZSB0byBiZSBjbGVhciBhYm91dCB0aGUgZGF0ZXMgb2YgYW55IGJvdW5kYXJpZXMgdXNlZCBieSBzdGF0aW5nIHRoZSBleGFjdCBjb2RlIHVzZWQsIGUuZy4gKip3ZDE5Y2QqKi4gQmVjYXVzZSBMRkIgZGF0YSBkb2VzIG5vdCBpbmNsdWRlIHRoaXMgaW5mb3JtYXRpb24gd2UgaGF2ZSBubyBndWFyYW50ZWUgdGhhdCB0aGUgYm91bmRhcmllcyBhbmQgR1NTIGNvZGVzIHdlIGpvaW4gd2lsbCBtYXRjaC4gIA0KDQpGb3J0dW5hdGVseSB3ZSBoYXZlIGFsc28gYmVlbiBwcm92aWRlZCB3aXRoIGNvbHVtbnMgcmVjb3JkaW5nIHRoZSAqKmVhc3RpbmcqKiwgYW5kICoqbm9ydGhpbmcqKiBvZiBlYWNoIGluY2lkZW50LiBXZSBjYW4gdXNlIHRob3NlIHRvIGNvbnZlcnQgYGxmYmAgaW50byBhbiBgc2ZgIG9iamVjdC4gVG8gYWNoaWV2ZSB0aGlzIHdlIHdpbGwgdXNlIHRoZSBgc3RfYXNfc2YoKWAgZnVuY3Rpb24gd2hpY2ggdGFrZXMgdGhlIGZvbGxvd2luZyBhcmd1bWVudHM6DQoNCmBuZXdfb2JqZWN0IDwtIHN0X2FzX3NmKHggPSBpbnB1dF9kYXRhX2ZyYW1lLCBjb29yZHMgPSBjKCJ4X2Nvb3JkaW5hdGVfY29sdW1uIiwgInlfY29vcmRpbmF0ZV9jb2x1bW4iKSwgY3JzID0gMjc3MDApYA0KDQojIyMgRXhlcmNpc2UgLSBjcmVhdGUgc3BhdGlhbCBkYXRhDQoNCiogQ3JlYXRlIGEgbmV3IG9iamVjdCBjYWxsZWQgYGxmYl9zZmAgYnkgY29udmVydGluZyBgbGZiYCB1c2luZyB0aGUgYHN0X2FzX3NmKClgIGZ1bmN0aW9uLg0KKiBVc2UgYGdsaW1wc2UoKWAgb3IgYGhlYWQoKWAgdG8gdmlldyBgbGZiX3NmYCBzdHJ1Y3R1cmUuDQoNCioqU29sdXRpb24qKg0KYGBge3J9DQpsZmJfc2YgPC0gc3RfYXNfc2YoeCA9IGxmYiwgY29vcmRzID0gYygiZWFzdGluZyIsICJub3J0aGluZyIpLCBjcnMgPSAyNzcwMCkNCmhlYWQobGZiX3NmKQ0KYGBgDQoNCmBzdF9hc19zZigpYCBjb252ZXJ0ZWQgdGhlICoqZWFzdGluZyoqIGFuZCAqKm5vcnRoaW5nKiogY29sdW1ucyB0byBzaW1wbGUgZmVhdHVyZSBnZW9tZXRyaWVzIGFuZCBjcmVhdGVkIGEgbmV3IGNvbHVtbiBjYWxsZWQgKipnZW9tZXRyeSoqIHdoaWNoIGhvbGRzIHNwYXRpYWwgaW5mb3JtYXRpb24gZm9yIGVhY2ggcm93LiANCk5vdyB0aGF0IGBsZmJgIGlzIGEgc3BhdGlhbCBvYmplY3Qgd2UgY2FuIHBsb3QgaXQgdXNpbmcgdGhlIGB0bWFwYCBwYWNrYWdlLiBGb3Igbm93IHdlIHdpbGwgdXNlIHRoZSBgcXRtKClgIGZ1bmN0aW9uIHdoaWNoIGNyZWF0ZXMgYSBxdWljayBtYXAsIHVzaW5nIGB0bWFwJ3NgIGRlZmF1bHQgc2V0dGluZ3MuIGBxdG0oKWAgb25seSBuZWVkcyB0byBiZSBzdXBwbGllZCB3aXRoIGEgc2ltcGxlIGZlYXR1cmUgb2JqZWN0IGFuZCBpcyB2ZXJ5IHVzZWZ1bCBmb3IgcXVpY2tseSBpbnNwZWN0aW5nIHlvdXIgZGF0YS4gDQoNCiMjIyBFeGVyY2lzZSAtIHF1aWNrIHN0YXRpYyBtYXBzDQoNCiogUGxvdCBsZmJfc2YgdXNpbmcgdGhlIGBxdG0oKWAgZnVuY3Rpb24uDQoNCioqU29sdXRpb24qKg0KYGBge3J9DQpxdG0obGZiX3NmKQ0KYGBgDQoNCg0KV2UgY2FuIGFsc28gY3JlYXRlIGludGVyYWN0aXZlIG1hcHMgdXNpbmcgdGhlIGB0bWFwYCwgcGFja2FnZSBieSBydW5uaW5nIGB0bWFwX21vZGUoInZpZXciKWAgYmVmb3JlIGV4ZWN1dGluZyBgcXRtKClgLiANClRvIHJldmVyc2UgaXQgYW5kIGdvIGJhY2sgdG8gc3RhdGljIG1hcHMgdXNlIGB0bWFwX21vZGUoInBsb3QiKWAuDQoNCiMjIyBFeGVyY2lzZSAtIHF1aWNrIGludGVyYWN0aXZlIG1hcHMNCg0KKiBNYWtlIGFuIGludGVyYWN0aXZlIG1hcCBvZiBgbGZiX3NmYCB1c2luZyB0aGUgYHF0bSgpYCBmdW5jdGlvbiBhbmQgYHRtYXBfbW9kZSgidmlldyIpYC4NCg0KKipTb2x1dGlvbioqDQpgYGB7ciwgbWVzc2FnZT1GQUxTRX0NCnRtYXBfbW9kZSgidmlldyIpDQpxdG0obGZiX3NmKQ0KYGBgDQoNCiMjIEZpbHRlcmluZyBieSBHU1MgY29kZQ0KDQpJdCBsb29rcyBsaWtlIHNvbWUgb2YgdGhlIGxvY2F0aW9ucyBhcmUgbG9jYXRlZCBvdXRzaWRlIG9mIExvbmRvbiwgaG93ZXZlciB3ZSBhcmUgb25seSBpbnRlcmVzdGVkIGluIGluY2lkZW50cyB3aXRoaW4gdGhlIExvY2FsIEF1dGhvcml0eSBEaXN0cmljdHMgbWFraW5nIHVwIEdyZWF0ZXIgTG9uZG9uLiBUbyByZW1vdmUgYWxsIHBvaW50cyBvdXRzaWRlIG9mIExvbmRvbiB3ZSB3aWxsIGhhdmUgdG8gZmlyc3QgaW1wb3J0IHRoZSBMQUQgYm91bmRhcmllcyB3aGljaCB3ZSBkb3dubG9hZGVkIGZyb20gdGhlIE9wZW4gR2VvZ3JhcGh5IFBvcnRhbCBhbmQgdGhlbiB1c2UgdGhlbSB0byBzcGF0aWFsbHkgZmlsdGVyIGBsZmJfc2ZgIGRhdGEuIA0KDQpTbyBmYXIgd2UgaGF2ZSBjcmVhdGVkIG91ciBvd24gYHNmYCBvYmplY3RzIGJ5IGFkZGluZyBhIGdlb21ldHJ5IGNvbHVtbi4gVGhlIExBRCBkYXRhIHNldCBpcyBhbHJlYWR5IGEgc3BhdGlhbCBvbmUgYW5kIGFzIHN1Y2ggd2UgY2FuIHVzZSB0aGUgYHN0X3JlYWQoKWAgZnVuY3Rpb24gZnJvbSB0aGUgYHNmYCBwYWNrYWdlIHRvIGltcG9ydCBpdC4gYHN0X3JlYWRgIGlzIGV4dHJlbWVseSB2ZXJzYXRpbGUgYW5kIGFibGUgdG8gaW1wb3J0IG1vc3Qgc3BhdGlhbCBkYXRhIGZvcm1hdHMgaW50byBSLiBUaGUgb25seSBhcmd1bWVudCB0aGF0IG5lZWRzIHRvIGJlIHN1cHBsaWVkIHRvIGBzdF9yZWFkYCBpcyB0aGUgZnVsbCBwYXRoIHRvIHRoZSBMQUQgYm91bmRhcmllcyANCg0KIyMjIEV4ZXJjaXNlIC0gbG9hZGluZyBzaGFwZWZpbGVzDQoNCiogVXNlIGBzdF9yZWFkKClgIHRvIGxvYWQgdGhlIExBRCBib3VuZGFyaWVzIHlvdSBkb3dubG9hZGVkIGF0IHRoZSBiZWdpbm5pbmcgb2YgdGhlIHR1dG9yaWFsLCBhc2BsYWRfMjAxOWAuDQoqIExBRCBwYXRoIC0gYGRhdGEvc2hwL0xvY2FsX0F1dGhvcml0eV9EaXN0cmljdHNfRGVjZW1iZXJfMjAxOV9Cb3VuZGFyaWVzX1VLX0JHQy9gDQogICAgICAgICAgICAgIGBMb2NhbF9BdXRob3JpdHlfRGlzdHJpY3RzX0RlY2VtYmVyXzIwMTlfQm91bmRhcmllc19VS19CR0Muc2hwYA0KKiBNYWtlIGEgc3RhdGljIG1hcCBvZiB0aGUgb2JqZWN0IHlvdSBoYXZlIGp1c3QgY3JlYXRlZCB1c2luZyBgcXRtKClgIGFuZCBzZXR0aW5nIGB0bWFwX21vZGUoInBsb3QiKWAuDQoNCioqU29sdXRpb24qKg0KYGBge3J9DQpsYWRfMjAxOSA8LSBzdF9yZWFkKCJkYXRhL3NocC9Mb2NhbF9BdXRob3JpdHlfRGlzdHJpY3RzX0RlY2VtYmVyXzIwMTlfQm91bmRhcmllc19VS19CR0MvTG9jYWxfQXV0aG9yaXR5X0Rpc3RyaWN0c19EZWNlbWJlcl8yMDE5X0JvdW5kYXJpZXNfVUtfQkdDLnNocCIpDQp0bWFwX21vZGUoInBsb3QiKQ0KcXRtKGxhZF8yMDE5KQ0KDQpgYGANCg0KTEFEIGJvdW5kYXJpZXMgaGF2ZSBsb2FkZWQgY29ycmVjdGx5IGJ1dCB0aGV5IGN1cnJlbnRseSBjb3ZlciBhbGwgb2YgdGhlIFVLIHdoZW4gYWxsIHdlIG5lZWQgaXMgTG9uZG9uLiBCZWNhdXNlIHNpbXBsZSBmZWF0dXJlIG9iamVjdHMgYXJlIGRhdGEgZnJhbWVzIHdpdGggYSBnZW9tZXRyeSBjb2x1bW4gYXR0YWNoZWQsIGFueSBvcGVyYXRpb25zIHRoYXQgd2Ugd291bGQgcGVyZm9ybSBvbiBhIG5vcm1hbCBkYXRhIGZyYW1lIGNhbiBhbHNvIGJlIHBlcmZvcm1lZCBvbiBhbiBvYmplY3Qgb2YgY2xhc3MgYHNmYC4gSGVyZSB3ZSB3aWxsIHVzZSB0aGUgYGRwbHlyOjpmaWx0ZXJgIGFuZCBgc3RyaW5ncjo6c3RyX2RldGVjdCgpYCBmcm9tIHRoZSB0aGUgYHRpZHl2ZXJzZWAgcGFja2FnZSB0byBvbmx5IGtlZXAgTEFEcyB3aG9zZSBHU1MgY29kZSBzdGFydHMgd2l0aCAiRTA5Ii4NCg0KDQojIyMgRXhlcmNpc2UgLSBmaWx0ZXIgc3BhdGlhbCBkYXRhIGJ5IHZhcmlhYmxlDQoNCiogSW5zcGVjdCBsYWRfMjAxOSB1c2luZyBgaGVhZCgpYCBvciBgZ2xpbXBzZSgpYCwgYW5kIGlkZW50aWZ5IHdoaWNoIGNvbHVtbiBob2xkcyB0aGUgR1NTIGNvZGVzIC0gaXQgc2hvdWxkIGVuZCBpbiAiY2QiLg0KKiBDcmVhdGUgYSBuZXcgb2JqZWN0IGNhbGxlZCBgbG9uZG9uX2xhZGAuIFVzZSBgZHBseXI6OmZpbHRlcmAgYWxvbmdzaWRlIGBzdHJpbmdyOjpzdHJfZGV0ZWN0KClgIHRvIG9ubHkga2VlcCBvYnNlcnZhdGlvbnMgd2hpY2ggaGF2ZSBhIEdTUyBjb2RlIHN0YXJ0aW5nIHdpdGggIkUwOSIuDQoqIFBsb3QgYGxvbmRvbl9sYWRgIHRvIHNlZSBpZiB0aGUgcmVzdWx0cyBsb29rIGNvcnJlY3QuDQoNCioqU29sdXRpb24qKg0KYGBge3J9DQpoZWFkKGxhZF8yMDE5KQ0KYGBgDQoNCmBgYHtyfQ0KbG9uZG9uX2xhZCA8LSBmaWx0ZXIobGFkXzIwMTksIHN0cl9kZXRlY3QobGFkMTljZCwgIkUwOSIpKQ0KcXRtKGxvbmRvbl9sYWQpDQpgYGANCg0KRmluYWxseSwgZm9yIHRoZSBuZXh0IHN0ZXAsIHdlIG9ubHkgbmVlZCB0aGUgb3V0ZXIgYm91bmRhcnkgb2YgTG9uZG9uIC0gYWxsIHRoZSBpbnRlcm5hbCBMQUQgYm91bmRhcmllcyBoYXZlIHRvIGJlIHJlbW92ZWQgYW5kIG9ubHkgdGhlIG91dGVyIGVkZ2VzIGtlcHQuIGBzZmAgaGFzIGEgZnVuY3Rpb24gZXhhY3RseSBmb3IgdGhpcyBwdXJwb3NlIGNhbGxlZCBgc3RfdW5pb24oKWAuIA0KSXQgb25seSB0YWtlcyBvbmUgYXJndW1lbnQsIHdoaWNoIGlzIHRoZSBgc2ZgIG9iamVjdCB3ZSB3YW50IHRvIHVuaW9uaXNlLiANCg0KIyMjIEV4ZXJjaXNlIC0gZGlzc29sdmUgYm91bmRhcmllcw0KDQoqIENyZWF0ZSBhIG5ldyBvYmplY3QgY2FsbGVkIGBsb25kb25fYm91bmRhcnlgIHVzaW5nIHRoZSBgc3RfdW5pb25gIGZ1bmN0aW9uLg0KKiBQbG90IGl0IHRvIGNoZWNrIHRoZSByZXN1bHRzLg0KDQoqKlNvbHV0aW9uKioNCmBgYHtyfQ0KbG9uZG9uX2JvdW5kYXJ5IDwtIHN0X3VuaW9uKGxvbmRvbl9sYWQpDQpxdG0obG9uZG9uX2JvdW5kYXJ5KQ0KYGBgDQoNCg0KIyMgU3BhdGlhbCBzdWJzZXR0aW5nIGFuZCBDUlMNCg0KSW4gYWRkaXRpb24gdG8gc3Vic2V0dGluZyBieSB2YWx1ZSwgYXMgd2UgZGlkIHdpdGggdGhlIExBRCBib3VuZGFyaWVzIGVhcmxpZXIsIHdlIGNhbiBhbHNvIHN1YnNldCBvYnNlcnZhdGlvbnMgYnkgZXZhbHVhdGluZyB0aGVpciBzcGF0aWFsIHJlbGF0aW9uc2hpcCB3aXRoIGFub3RoZXIgZGF0YSBzZXQuIFdlIGNhbiBmb3IgZXhhbXBsZSBzZWxlY3QgYWxsIExBRHMgd2hpY2ggYXJlIGZ1bGx5IHdpdGhpbiBXYWxlcywgZXZlcnkgT3V0cHV0IEFyZWEgaW50ZXJzZWN0ZWQgYnkgYSByaXZlciwgb3IgYWxsIGhvdXNlaG9sZHMgb3V0c2lkZSBvZiBjaXR5IGJvdW5kYXJpZXMuIFRoZXJlIGFyZSBhIG51bWJlciBvZiBkaWZmZXJlbnQgc3BhdGlhbCByZWxhdGlvbnNoaXBzIHdoaWNoIGNhbiBiZSB0ZXN0ZWQgYW5kIHVzZWQgdG8gc3Vic2V0IG9ic2VydmF0aW9ucy4NCg0KPGNlbnRlcj4NCiFbXShkYXRhL2ltZy9zcGF0aWFsX3JlbGF0aW9uLnBuZykgDQo8L2NlbnRlcj4NCg0KDQpgc2ZgIGhhcyBhbiBpbmJ1aWx0IGZ1bmN0aW9uIGNhbGxlZCBgc3RfZmlsdGVyKClgIHdoaWNoIHdlIGNhbiB1c2UgdG8gc3BhdGlhbGx5IHN1YnNldCBvYnNlcnZhdGlvbnMuDQpUaGUgZnVuY3Rpb24gdGFrZXMgc2V2ZXJhbCBhcmd1bWVudHM6DQoNCiogeCAtIGBzZmAgZGF0YSBmcmFtZSB3ZSB3YW50IHRvIHN1YnNldCAtIGBsZmJfc2ZgDQoqIHkgLSBgc2ZgIG9iamVjdCB1c2VkIHRvIGV2YWx1YXRlIHRoZSBzcGF0aWFsIHJlbGF0aW9uc2hpcCAtIGBsb25kb25fYm91bmRhcnlgDQoNCkJlZm9yZSBydW5uaW5nIGFueSBzcGF0aWFsIG9wZXJhdGlvbnMgb24gdHdvIHNwYXRpYWwgb2JqZWN0cyBpdCBpcyBhbHdheXMgd29ydGggY2hlY2tpbmcgaWYgdGhlaXIgY29vcmRpbmF0ZSByZWZlcmVuY2Ugc3lzdGVtcyAoQ1JTKSBtYXRjaC4gYHNmYCB3aWxsIHRocm93IGFuIGVycm9yIGlmIHRoYXQncyBub3QgdGhlIGNhc2UuIFRyeSBpdCBmb3IgeW91cnNlbGYgYmVsb3cuDQoNCiMjIyBFeGVyY2lzZSAtIHNwYXRpYWwgc3Vic2V0IHBhcnQgMSANCg0KKiBVc2UgYHN0X2ZpbHRlcigpYCB0byBzcGF0aWFsbHkgc3Vic2V0IGBsZmJfc2ZgIGJ5IHRlc3RpbmcgaXRzIHJlbGF0aW9uc2hpcCB3aXRoIGBsb25kb25fYm91bmRhcnlgLg0KDQoqKlNvbHV0aW9uOioqDQpgYGB7ciwgZXZhbD1GQUxTRX0NCmxmYl9zZiA8LSBzdF9maWx0ZXIoeCA9IGxmYl9zZiwgeSA9IGxvbmRvbl9ib3VuZGFyeSkNCg0KYGBgDQoNCllvdSBzaG91bGQgaGF2ZSBnb3QgYW4gZXJyb3IgaGVyZSBzYXlpbmcgYEVycm9yIGluICFpbmhlcml0cyh4LCAic2YiKSA6IHN0X2Nycyh4KSA9PSBzdF9jcnMoeSkgaXMgbm90IFRSVUVgLiBJdCBtZWFucyB0aGF0IG9iamVjdHMgeCBhbmQgeSBoYXZlIGRpZmZlcmVudCBDUlMuIFdlIGNhbiBzZWUgdGhpcyBmb3Igb3Vyc2VsdmVzIGJ5IHJ1bm5pbmcgdGhlIGBzdF9jcnMoKWAgZnVuY3Rpb24sIHdoaWNoIHJldHVybnMgdGhlIGNvb3JkaW5hdGUgcmVmZXJlbmNlIHN5c3RlbSBvZiBhbiBvYmplY3QuDQoNCiMjIyBFeGVyY2lzZSAgLSBjaGVjayBDUlMNCg0KKiBSdW4gYHN0X2NycygpYCBvbiBib3RoIGFuZCBgbGZiX3NmYCBhbmQgYGxvbmRvbl9ib3VuZGFyeWAgYW5kIGNvbXBhcmUgdGhlIHJlc3VsdHMuDQoNCioqU29sdXRpb246KioNCmBgYHtyfQ0Kc3RfY3JzKGxmYl9zZikNCg0Kc3RfY3JzKGxvbmRvbl9ib3VuZGFyeSkNCg0KYGBgDQoNCldlIGNhbiBzZWUgdGhhdCBgbG9uZG9uX2JvdW5kYXJ5YCBoYXMgbm90IGdvdCBhbiBFUFNHIGNvZGUsIGFuZCB0aGF0IHRoZSBwcm9qNHN0cmluZyBpbmZvcm1hdGlvbiBpcyBkaWZmZXJlbnQuIFdlIGNhbiAgc29sdmUgdGhpcyBwcm9ibGVtIGJ5IHRyYW5zZm9ybWluZyBgbG9uZG9uX2JvdW5kYXJ5YCdzIENSUyB0byBtYXRjaCB0aGF0IG9mIGBsZmJfc2ZgLCBzaW1wbHkgYnkgdXNpbmcgdGhlIGNvcnJlY3QgRVBTRyBjb2RlLiBUbyBkbyBzbyB3ZSB3aWxsIHVzZSB0aGUgYHN0X3RyYW5zZm9ybSgpYCBmdW5jdGlvbiB3aGljaCB0YWtlcyB0d28gYXJndW1lbnRzOiANCg0KKiB4IC0gYHNmYCBvYmplY3QgdG8gYmUgdHJhbnNmb3JtZWQNCiogY3JzIC0gRVBTRyBjb2RlIHRoYXQgd2Ugd2FudCB0byB0cmFuc2Zvcm0gb3VyIGRhdGEgdG8gLSBCTkcgaXMgMjc3MDAuDQoNCiMjIyBFeGVyY2lzZSAtIHRyYW5zZm9ybSBDUlMNCg0KKiBSdW4gYHN0X3RyYW5zZm9ybSgpYCB0byB0cmFuc2Zvcm0gYW5kIG92ZXJ3cml0ZSBgbG9uZG9uX2JvdW5kYXJ5YC4gUmVtZW1iZXIgdG8gc2V0IHRoZSBjb3JyZWN0IENSUy4gDQoqIFJ1biBgc3RfY3JzKClgIG9uIGBsZmJfc2ZgIGFuZCBuZXdseSB0cmFuc2Zvcm1lZCBgbG9uZG9uX2JvdW5kYXJ5YCBhbmQgY29tcGFyZSB0aGUgcmVzdWx0cy4NCg0KKipTb2x1dGlvbjoqKg0KDQpgYGB7cn0NCmxvbmRvbl9ib3VuZGFyeSA8LSBzdF90cmFuc2Zvcm0obG9uZG9uX2JvdW5kYXJ5LCBjcnMgPSAyNzcwMCkNCg0Kc3RfY3JzKGxmYl9zZikNCnN0X2Nycyhsb25kb25fYm91bmRhcnkpDQpgYGANCg0KDQpOb3cgdGhhdCB0aGUgQ1JTIGFyZSBtYXRjaGluZyB3ZSBzaG91bGQgYmUgYWJsZSB0byBzcGF0aWFsbHkgc3Vic2V0IGBsZmJfc2ZgLg0KDQojIyMgRXhlcmNpc2UgLSBzcGF0aWFsIHN1YnNldCBwYXJ0IDIgDQoNCiogVXNlIGBzdF9maWx0ZXJgIHRvIHNwYXRpYWxseSBzdWJzZXQgYGxmYl9zZmAgYnkgdGVzdGluZyBpdHMgcmVsYXRpb25zaGlwIHdpdGggYGxvbmRvbl9ib3VuZGFyeWAuIE92ZXJ3cml0ZSBgbGZiX3NmYCB3aXRoIHRoZSBzdWJzZXQgZGF0YS4NCiogUGxvdCBpdCB0byBjaGVjayBpZiB0aGUgcmVzdWx0cyBhcmUgY29ycmVjdC4NCg0KKipTb2x1dGlvbjoqKg0KYGBge3J9DQpsZmJfc2YgPC0gc3RfZmlsdGVyKHggPSBsZmJfc2YsIHkgPSBsb25kb25fYm91bmRhcnkpDQpxdG0obGZiX3NmKQ0KYGBgDQoNCg0KIyMgU3BhdGlhbCBhbmQgbm9uLXNwYXRpYWwgam9pbnMNCg0KU2ltcGxlIGZlYXR1cmVzIGRhdGEgY2FuIGJlIGpvaW5lZCB0byBvdGhlciBkYXRhIHNldHMgaW4gdHdvIHdheXMuIFdlIGNhbiBlaXRoZXIgdXNlIGEgdHJhZGl0aW9uYWwsIFNRTCBsaWtlIGpvaW4sIGJhc2VkIG9uIGEgdmFsdWUgc2hhcmVkIGFjcm9zcyB0aGUgZGF0YSBzZXRzIG9yLCBzaW5jZSB3ZSBoYXZlIGEgZ2VvbWV0cnkgY29sdW1uLCBvbiB0aGUgc3BhdGlhbCByZWxhdGlvbnNoaXAgYmV0d2VlbiB0aGUgZGF0YSBzZXRzLiBUaGlzIGlzIGtub3duIGFzIGEgc3BhdGlhbCBqb2luLCB3aGVyZSB2YXJpYWJsZXMgZnJvbSBvbmUgZGF0YSBzZXQgYXJlIGpvaW5lZCB0byBhbm90aGVyIG9uZSBvbmx5IG9uIHRoZSBiYXNpcyBvZiB0aGVpciBzcGF0aWFsIHJlbGF0aW9uc2hpcC4gVGhlIG1vc3QgY29tbW9ubHkgdXNlZCBvcGVyYXRpb24gaXMga25vd24gYXMgYSBQb2ludC1pbi1Qb2x5Z29uIGpvaW4gd2hlcmUgZGF0YSBmcm9tIGEgcG9seWdvbiBpcyBqb2luZWQgdG8gdGhlIHBvaW50cyB3aXRoaW4gdGhlbS4NCg0KPGNlbnRlcj4NCiFbXShkYXRhL2ltZy9zcGF0aWFsX2pvaW4ucG5nKQ0KPC9jZW50ZXI+ICANCiAgDQogIA0KDQpJbiBgc2ZgIHNwYXRpYWwgam9pbnMgYXJlIGhhbmRsZWQgdXNpbmcgdGhlIGBzdF9qb2luKClgIGZ1bmN0aW9uIHdpdGggYXJndW1lbnRzOg0KDQoqIHggLSBgc2ZgIG9iamVjdCB0byB3aGljaCB3ZSBhcmUgam9pbmluZyBkYXRhIChMSFMgaW4gU1FMKQ0KKiB5IC0gYHNmYCBvYmplY3Qgd2hvc2UgdmFyaWFibGVzIGFyZSBiZWluZyBqb2luZWQgKFJIUyBpbiBTUUwpDQoNCldlIHdpbGwgYmUgam9pbmluZyB0aGUgTWlkZGxlIFN1cGVyIE91dHB1dCBBcmVhcyB0byBMRkIgbG9jYXRpb25zLCB3aGljaCB3aWxsIHRoZW4gYWxsb3cgdXMgdG8gZ3JvdXAgYW5kIHBsb3QgZGF0YSBhdCBNU09BIGxldmVsLg0KDQojIyMgRXhlcmNpc2UgLSBzcGF0aWFsIGpvaW5zDQoNCiogUmVhZCBpbiBgZGF0YS9zaHAvTVNPQV8yMDExX2xvbmRvbi9tc29hXzIwMTFfZXdfYmdjLnNocGAgYXMgYG1zb2FfbG9uZG9uYCAtIHVzZSBgc3RfcmVhZCgpYA0KKiBDaGVjayBpZiBgbXNvYV9sb25kb25gJ3MgQ1JTIGFuZCB0aGF0IG9mIGBsZmJfc2ZgIG1hdGNoLiBUcmFuc2Zvcm0gYG1zb2FfbG9uZG9uYCBpZiBuZWNlc3NhcnkuDQoqIENyZWF0ZSBhIG5ldyBvYmplY3QgY2FsbGVkIGBsZmJfbXNvYV9zZmAgYnkgcnVubmluZyBgc3Rfam9pbigpYCBiZXR3ZWVuIGBsZmJfc2ZgIGFuZCBgbXNvYV9sb25kb25gDQoqIEluc3BlY3QgeW91ciBuZXcgb2JqZWN0IHVzaW5nIGBoZWFkKClgIG9yIGBnbGltcHNlKClgIHRvIHNlZSB3aGF0IGNvbHVtbnMgaGF2ZSBiZWVuIGFkZGVkLg0KDQpgYGB7cn0NCm1zb2FfbG9uZG9uIDwtIHN0X3JlYWQoImRhdGEvc2hwL01TT0FfMjAxMV9sb25kb24vbXNvYV8yMDExX2V3X2JnYy5zaHAiKQ0KDQpzdF9jcnMobXNvYV9sb25kb24pDQoNCnN0X2NycyhsZmJfc2YpDQoNCm1zb2FfbG9uZG9uIDwtIHN0X3RyYW5zZm9ybShtc29hX2xvbmRvbiwgY3JzID0gMjc3MDApDQoNCmxmYl9tc29hX3NmIDwtIHN0X2pvaW4obGZiX3NmLCBtc29hX2xvbmRvbikNCg0KaGVhZChsZmJfbXNvYV9zZikNCmBgYA0KDQoNCkJlZm9yZSB3ZSBwcm9jZWVkIGxldCdzIGNoZWNrIGlmIGFsbCBwb2ludHMgd2VyZSBzdWNjZXNmdWxseSBqb2luZWQuIElmIHRoYXQncyBub3QgdGhlIGNhc2UsIHNvbWUgb2JzZXJ2YXRpb25zIHdpbGwgYmUgYE5BYC4gDQoNCiMjIyBFeGVyY2lzZSAtIHJlbW92ZSBOQSBqb2lucw0KDQoqIFVzZSBgZmlsdGVyKClgIGFuZCBgaXMubmEoKWAgb24gdGhlIGBtc29hMTFjZGAgdmFyaWFibGUgb2YgYGxmYl9tc29hX3NmYCB0byBjaGVjayBpZiBhbnkgcG9pbnRzIGRpZCBub3Qgam9pbiBjb3JyZWN0bHkuIA0KKiBTYXZlIHRob3NlIHRvIGEgbmV3IG9iamVjdCBjYWxsZWQgYGxmYl9tc29hX3NmX25hYCANCiogQ3JlYXRlIGFuIGludGVyYWN0aXZlIG1hcCBvZiB0aGUgcG9pbnRzIGFuZCBzZWUgd2h5IHRoZXkgZGlkIG5vdCBqb2luIGNvcnJlY3RseS4NCiogUmVtb3ZlIGFsbCBgTkFgIG9ic2VydmF0aW9ucyBmcm9tIGBsZmJfbXNvYV9zZmBhbmQgb3ZlcndyaXRlIGl0IC0gdXNlIGAhaXMubmFgIHRvIGZpbmQgdGhlIGNvcnJlY3Qgc3Vic2V0Lg0KDQpTb2x1dGlvbg0KYGBge3J9DQp0bWFwX21vZGUoInZpZXciKQ0KbGZiX3NmX25hIDwtIGZpbHRlcihsZmJfbXNvYV9zZiwgaXMubmEobXNvYTExY2QpKQ0KcXRtKGxmYl9zZl9uYSkNCg0KbGZiX21zb2Ffc2YgPC0gZmlsdGVyKGxmYl9tc29hX3NmLCAhaXMubmEobXNvYTExY2QpKQ0KYGBgDQoNCg0KTm93IHRoYXQgYG1zb2ExMWNkYCBpcyBhdHRhY2hlZCB0byBvdXIgb2JzZXJ2YXRpb25zIHdlIGNhbiBjcmVhdGUgc29tZSBzdW1tYXJ5IHN0YXRpc3RpY3MgZm9yIGVhY2ggTVNPQS4gQXMgbWVudGlvbmVkIGJlZm9yZSwgd2UgY2FuIHVzZSBzdGFuZGFyZCBgdGlkeXZlcnNlYCBmdW5jdGlvbnMgb24gYHNmYCBvYmplY3RzLiBIZXJlLCB3ZSB3aWxsIHVzZSBgZHBseXJgIHRvIGNhbGN1bGF0ZSB0aGUgdG90YWwgbnVtYmVyIG9mIGluY2lkZW50cyBhbmQgdGhlaXIgY29zdCwgYW5kIHRoZW4gdXNlIGEgbm9uIHNwYXRpYWwgam9pbiB0byBhdHRhY2ggdGhvc2UgcmVzdWx0cyB0byBNU09BIGJvdW5kYXJpZXMuIEF0IHRoaXMgc3RhZ2Ugd2Ugbm8gbG9uZ2VyIG5lZWQgdGhlIGdlb21ldHJ5IGNvbHVtbiBmb3IgZWFjaCBMRkIgaW5jaWRlbnQgYXMgYSkgd2UncmUgbm90IHBlcmZvcm1pbmcgYW55IHNwYXRpYWwgb3BlcmF0aW9ucyBvbiBvdXIgcG9pbnRzLCBhbmQgYikgdGhlIGdlb21ldHJ5IGNvbHVtbiBjYW4gc2xvdyBkb3duL2ludGVycnVwdCB0aGUgYGRwbHlyOjpncm91cF9ieWAgZnVuY3Rpb24gd2hpY2ggd2Ugd2lsbCBiZSB1c2luZy4gVG8gcmVtb3ZlIHRoZSBnZW9tZXRyeSBjb2x1bW4gd2UgY2FuIHVzZSB0aGUgYHN0X2Ryb3BfZ2VvbWV0cnkoKWAgZnVuY3Rpb24gZGlyZWN0bHkgaW4gdGhlIGRwbHlyIHBpcGUuIA0KDQojIyMgRXhlcmNpc2UgLSBNU09BIHN1bW1hcnkgc3RhdGlzdGljcw0KDQoqIFRoZSBzdGVwIHJlcXVpcmVzIHlvdSB0byBiZSBmYW1pbGlhciB3aXRoIGBkcGx5cmAncyBtb3JlIGFkdmFuY2VkIGZ1bmN0aW9ucy4gSWYgeW91IGFyZSBzdHJ1Z2dsaW5nIHdpdGggdGhpcyBzdGVwIGxvYWQgYGRhdGEvZ3BrZy9tc29hX2xmYi5ncGtnYCBhcyBgbXNvYV9sZmJgIHVzaW5nIGBzdF9yZWFkYC4NCiogVXNlIGBzdF9kcm9wX2dlb21ldHJ5KClgIG9uIGBsZmJfbXNvYV9zZmAgdG8gcmVtb3ZlIGdlb21ldHJ5IGRhdGEuIA0KKiBDcmVhdGUgc3VtbWFyeSBzdGF0aXN0aWNzIHBlciBNU09BIC0gc3VtIG9mIGNvc3RfZ2JwIGFzIHRvdGFsX2Nvc3QgKHVzZSBgbmEucm0gPSBUUlVFYCksIGFuZCB0aGUgdG90YWwgbnVtYmVyIG9mIGluY2lkZW50cyBhcyBuX2Nhc2VzLiBZb3Ugd2lsbCBuZWVkIHRvIHVzZSBgZ3JvdXBfYnkoKWAgYW5kIGBzdW1tYXJpc2UoKWANCiogQ3JlYXRlIGEgbmV3IGNvbHVtbiBjYWxsZWQgYGNvc3RfcGVyX2luY2lkZW50YCB1c2luZyBgbXV0YXRlYCAtIGB0b3RhbF9jb3N0YCBkaXZpZGVkIGJ5IGBuX2Nhc2VzYC4NCiogSm9pbiBgbGZiX21zb2Ffc3RhdHNgIHRvIGBtc29hX2xvbmRvbmAsIHVzaW5nIGBsZWZ0X2pvaW4oKWAgYW5kIGNyZWF0ZSBhIG5ldyBvYmplY3QgYG1zb2FfbGZiYA0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRX0NCmxmYl9tc29hX3N0YXRzIDwtIGxmYl9tc29hX3NmICU+JSANCiAgICAgICAgICAgICAgICAgIHN0X2Ryb3BfZ2VvbWV0cnkoKSAlPiUgDQogICAgICAgICAgICAgICAgICBncm91cF9ieShtc29hMTFjZCkgJT4lIA0KICAgICAgICAgICAgICAgICAgc3VtbWFyaXNlKHRvdGFsX2Nvc3QgPSBzdW0oY29zdF9nYnAsIG5hLnJtPVRSVUUpLCBuX2Nhc2VzID0gbigpKSAlPiUgDQogICAgICAgICAgICAgICAgICBtdXRhdGUoY29zdF9wZXJfaW5jaWRlbnQgPSB0b3RhbF9jb3N0L25fY2FzZXMpDQogICAgICAgICAgICAgICAgICAgICAgICAgDQoNCm1zb2FfbGZiIDwtIGxlZnRfam9pbihtc29hX2xvbmRvbiwgbGZiX21zb2Ffc3RhdHMpDQptc29hX2xmYg0KYGBgDQoNCmBgYHtyLCBldmFsPUZBTFNFfQ0KbXNvYV9sZmIgPC0gc3RfcmVhZCgiZGF0YS9ncGtnL21zb2FfbGZiLmdwa2ciKSANCg0KYGBgDQoNCkF0IHRoaXMgc3RhZ2UgaXQgaXMgYSBnb29kIGlkZWEgdG8gc2F2ZSBvdXIgZGF0YS4gV2UgY2FuIGRvIHRoaXMgdXNpbmcgdGhlIGBzdF93cml0ZSgpYCBmdW5jdGlvbi4gSXQgbmVlZHMgYW4gYHNmYCBvYmplY3QgYW5kIHRoZSBwYXRoIGFuZCBuYW1lIG9mIHRoZSBvdXRwdXQuDQoNCiMjIyBFeGVyY2lzZSAtIHNhdmUgZGF0YSB0byBncGtnDQoNCiogQ29weSBhbmQgZXhlY3V0ZSB0aGUgZm9sbG93aW5nIGNvZGUgdG8gc2F2ZSB5b3VyIGRhdGE6IGBzdF93cml0ZShtc29hX2xmYiwib3V0cHV0L21zb2FfbGZiLmdwa2cpYA0KDQoNCiMgTWFraW5nIGJldHRlciBtYXBzDQoNCk5vdyB0aGF0IHdlIGhhdmUgcHJvY2Vzc2VkIG91ciBkYXRhIHdlIGNhbiBzdGFydCBtYXBwaW5nIGl0LiBTbyBmYXIgd2UgaGF2ZSBvbmx5IHVzZWQgdGhlIGBxdG0oKWAgZnVuY3Rpb24gZnJvbSB0aGUgYHRtYXBgIHBhY2thZ2UuIFRoaXMgY3JlYXRlcyBhIGRlZmF1bHQgbWFwIGFuZCBpcyBncmVhdCB3aGVuIGFsbCB3ZSB3YW50IHRvIGRvIGlzIHF1aWNrbHkgdmlzdWFsaXNlIG91ciBkYXRhLiBUaGUgZnVsbCByYW5nZSBvZiBgdG1hcGAgZnVuY3Rpb25zIGdpdmVzIHVzIGNvbnRyb2wgb3ZlciBhbGwgZWxlbWVudHMgb2YgdGhlIGZpbmFsIHBsb3QgYW5kIGFsbG93cyB1cyB0byBjcmVhdGUgaGlnaCBxdWFsaXR5IG1hcHMuDQoNCmBgYHtyLCBmaWcuYWxpZ249J2NlbnRlcicsIG1lc3NhZ2U9RkFMU0V9DQp0bWFwX21vZGUoInBsb3QiKQ0KDQp0bV9zaGFwZShtc29hX2xmYikgKyANCiAgdG1fcG9seWdvbnMoY29sID0gInRvdGFsX2Nvc3QiLCBib3JkZXIuY29sID0gIiM0YTQ5NDkiLCBsd2QgPSAwLjA1LCB0aXRsZSA9ICJUb3RhbCBjb3N0ICjCoykiLCBwYWxldHRlID0gIkJsdWVzIiwgY29udHJhc3QgPSAxLCBsZWdlbmQuaGlzdCA9IFRSVUUsDQogICAgICAgIGxhYmVscyA9IGMoIjAgLSAyLDAwMCIsICI+MiwwMDAgLSA0LDAwMCIsICI+NCwwMDAgLSA2LDAwMCIsICI+NiwwMDAgLSA4LDAwMCIsICI+OCwwMDAgLSAxMCwwMDAiLCANCiAgICAgICAgICAgICAgICAgICAiPjEwLDAwMCAtIDEyLDAwMCIsICI+MTIsMDAwIC0gMTQsMDAwIikpICsNCiAgdG1fc2NhbGVfYmFyKHBvc2l0aW9uID0gYygwLDApLCB0ZXh0LnNpemUgPSAwLjcpICsNCiAgdG1fbGF5b3V0KG1haW4udGl0bGUgPSAiQ29zdCBvZiBhbmltYWwgcmVsYXRlZCBpbmNpZGVudHMgcGVyIE1TT0EsIGJldHdlZW4gMjAwOSBhbmQgMjAyMCIsICBtYWluLnRpdGxlLnBvc2l0aW9uID0gIGMoMCwwKSwgbWFpbi50aXRsZS5zaXplID0gMSwgbWFpbi50aXRsZS5mb250ZmFjZSA9ICJib2xkIiwgZnJhbWUgPSBGQUxTRSwgbGVnZW5kLnBvc2l0aW9uICA9IGMoMC4wOCwwLjE4KSwNCiAgICAgICAgICAgIGlubmVyLm1hcmdpbnMgPSBjKDAuMSwwLjA1LDAuMSwwLjAyKSwgbGVnZW5kLm91dHNpZGUgPSBUUlVFLCBsZWdlbmQudGl0bGUuc2l6ZSAgPSAxLCBsZWdlbmQudGV4dC5zaXplID0gIDAuNywgdGl0bGUuc25hcC50by5sZWdlbmQgPSBGQUxTRSkgKw0KICB0bV9zaGFwZShsb25kb25fYm91bmRhcnkpICsgdG1fYm9yZGVycyhjb2wgPSAiYmxhY2siLCBsd2QgPSAwLjI1KQ0KYGBgDQoNCg0KYHRtYXBgIGZvbGxvd3Mgc2ltaWxhciBwcmluY2lwbGVzIHRvIGBnZ3Bsb3QyYCwgd2hlcmUgd2UgZmlyc3Qgc3BlY2lmeSB0aGUgZGF0YSBzb3VyY2UgLSBgdG1fc2hhcGVgLCB0aGVuIHRoZSBhZXN0aGV0aWNzIG9mIHRoZSBwbG90IC0gYHRtX3BvbHlnb25zYCwgYHRtX2RvdHNgLCBldGMuLCBhbmQgdGhlbiB3ZSBtYWtlIGFueSBmaW5hbHkgYWRqdXN0bWVudHMgLSBgdG1fbGF5b3V0YC4gQWxsIGZ1bmN0aW9ucyBuZWVkIHRvIGJlIGNvbm5lY3RlZCB1c2luZyB0aGUgYCtgIHN5bWJvbC4NCg0KKiBgdG1fc2hhcGUoKWAgLSBgc2ZgIG9iamVjdCB3aGljaCB5b3Ugd2FudCB0byBwbG90DQoqIGB0bV9maWxsKClgLCBgdG1fYm9yZGVycygpYCwgYHRtX3BvbHlnb25zKClgLCBgdG1fZG90cygpYCAtIHR5cGVzIG9mIG91dHB1dA0KKiBgdG1fbGF5b3V0KClgIC0gY29udHJvbHMgbGF5b3V0IG9mIHRoZSBtYXAsIHRpdGxlcywgbGFiZWxzLCBldGMuDQoNCmB0bWFwYCBzeW50YXg6IGB0bV9zaGFwZShzZl9vYmplY3QpICsgdG1fYm9yZGVycyhjb2wgPSBlaXRoZXIgImNvbG91ciIgb3IgbmFtZSBvZiBjb2x1bW4gd2hpY2ggd2Ugd2FudCB0byBwbG90KSArIHRtX2xheW91dChtYWluLnRpdGxlID0gInRpdGxlIG9mIHlvdXIgbWFwIilgDQoNCiMjIyBHdWlkZWQgZXhlcmNpc2UgLSBtYXBwaW5nDQoNClN0YXJ0IGJ5IHNwZWNpZnlpbmcgd2hpY2ggYHNmYCBvYmplY3QgaXMgYmVpbmcgbWFwcGVkIGluIGB0bV9zaGFwZSgpYCBhbmQgd2hhdCBjb2x1bW4gaG9sZHMgdGhlIHZhbHVlcyB0byBiZSB2aXN1YWxpc2VkLiBXZSB3aWxsIGFsc28gY2hhbmdlIHRoZSBsZWdlbmQncyB0aXRsZS4NCmBgYHtyfQ0KdG1fc2hhcGUobXNvYV9sZmIpICsgDQogIHRtX3BvbHlnb25zKGNvbCA9ICJjb3N0X3Blcl9pbmNpZGVudCIsIHRpdGxlID0gIkNvc3QgcGVyIEluY2lkZW50ICjCoykiKQ0KYGBgDQoNCk5vdyBsZXQncyBhZGQgYGxvbmRvbl9ib3VuZGFyeWAgdG8gaGF2ZSBhIHRoaWNrZXIgbGluZSBhcm91bmQgTG9uZG9uLg0KYGBge3J9DQp0bV9zaGFwZShtc29hX2xmYikgKyANCiAgdG1fcG9seWdvbnMoY29sID0gImNvc3RfcGVyX2luY2lkZW50IiwgdGl0bGUgPSAiQ29zdCBwZXIgSW5jaWRlbnQgKMKjKSIpICsgDQogIHRtX3NoYXBlKGxvbmRvbl9ib3VuZGFyeSkgKyB0bV9ib3JkZXJzKGNvbCA9ICJibGFjayIpDQpgYGANCg0KTmV4dCB3ZSB3aWxsIGFkZCBhIHNjYWxlIGJhciBhbmQgcG9zaXRpb24gaXQgaW4gdGhlIGJvdHRvbSBsZWZ0IGNvcm5lci4NCmBgYHtyfQ0KdG1fc2hhcGUobXNvYV9sZmIpICsgDQogIHRtX3BvbHlnb25zKGNvbCA9ICJjb3N0X3Blcl9pbmNpZGVudCIsIHRpdGxlID0gIkNvc3QgcGVyIEluY2lkZW50ICjCoykiKSArIA0KICB0bV9zaGFwZShsb25kb25fYm91bmRhcnkpICsgdG1fYm9yZGVycyhjb2wgPSAiYmxhY2siKSArDQogIHRtX3NjYWxlX2Jhcihwb3NpdGlvbiA9IGMoMCwwKSkNCmBgYA0KDQpXZSBjYW4gbm93IHJlbW92ZSB0aGUgYmxhY2sgZnJhbWUgZnJvbSB0aGUgbWFwIGFuZCBhZGQgYSB0aXRsZSB0byBvdXIgbWFwLg0KYGBge3J9DQp0bV9zaGFwZShtc29hX2xmYikgKyANCiAgdG1fcG9seWdvbnMoY29sID0gImNvc3RfcGVyX2luY2lkZW50IiwgdGl0bGUgPSAiQ29zdCBwZXIgSW5jaWRlbnQgKMKjKSIpICsgDQogIHRtX3NoYXBlKGxvbmRvbl9ib3VuZGFyeSkgKyB0bV9ib3JkZXJzKGNvbCA9ICJibGFjayIpICsNCiAgdG1fc2NhbGVfYmFyKHBvc2l0aW9uID0gYygwLDApKSArDQogICB0bV9sYXlvdXQodGl0bGUgPSAiQXZlcmFnZSBjb3N0IG9mIGFuaW1hbCByZWxhdGVkIGluY2lkZW50cyBiZXR3ZWVuIDIwMDkgYW5kIDIwMjAiLCAgDQogICAgICAgICAgICBmcmFtZSA9IEZBTFNFKQ0KYGBgDQoNCkFsbCBvZiB0aGUgbWFwIGVsZW1lbnRzIGFyZSBub3cgdmlzaWJsZSBidXQgdGhleSdyZSBub3QgaW4gdGhlIHJpZ2h0IHBsYWNlLiBXZSBjYW4gc29sdmUgdGhpcyBieSBpbmNyZWFzaW5nIHRoZSBtYXJnaW5zIGFyb3VuZCBvdXIgbWFwLiBUaGlzIHdpbGwgYWxsb3cgdGhlIHRpdGxlIGFuZCB0aGUgbGVnZW5kIHRvIG1vdmUgb3V0d2FyZHMuDQoNCmBgYHtyfQ0KdG1fc2hhcGUobXNvYV9sZmIpICsgDQogIHRtX3BvbHlnb25zKGNvbCA9ICJjb3N0X3Blcl9pbmNpZGVudCIsIHRpdGxlID0gIkNvc3QgcGVyIEluY2lkZW50ICjCoykiKSArIA0KICB0bV9zaGFwZShsb25kb25fYm91bmRhcnkpICsgdG1fYm9yZGVycyhjb2wgPSAiYmxhY2siKSArDQogIHRtX3NjYWxlX2Jhcihwb3NpdGlvbiA9IGMoMCwwKSkgKw0KICAgdG1fbGF5b3V0KHRpdGxlID0gIkF2ZXJhZ2UgY29zdCBvZiBhbmltYWwgcmVsYXRlZCBpbmNpZGVudHMgYmV0d2VlbiAyMDA5IGFuZCAyMDIwIiwgIA0KICAgICAgICAgICAgZnJhbWUgPSBGQUxTRSwgaW5uZXIubWFyZ2lucyA9IGMoMC4xLDAuMSwwLjEsMC4xNSkpDQoNCmBgYA0KDQpXZSBjYW4gYWxzbyBtYW51YWxseSBjaGFuZ2UgdGhlIGxlZ2VuZCBsYWJlbHMgdG8gZW5zdXJlIHRoZXJlIGFyZSBubyBvdmVybGFwcGluZyB2YWx1ZXMuDQpgYGB7cn0NCnRtX3NoYXBlKG1zb2FfbGZiKSArIA0KICB0bV9wb2x5Z29ucyhjb2wgPSAiY29zdF9wZXJfaW5jaWRlbnQiLCB0aXRsZSA9ICJDb3N0IHBlciBJbmNpZGVudCAowqMpIiwNCiAgICAgICAgICAgICAgbGFiZWxzID0gYygiMCAtIDIwMCIsICI+MjAwIC0gNDAwIiwgIj40MDAgLSA2MDAiLCAiPjYwMCAtIDgwMCIsICI+ODAwIC0gMSwwMDAiLCANCiAgICAgICAgICAgICAgICAgICAiPjEsMDAwIC0gMSwyMDAiKSkgKyANCiAgdG1fc2hhcGUobG9uZG9uX2JvdW5kYXJ5KSArIHRtX2JvcmRlcnMoY29sID0gImJsYWNrIikgKw0KICB0bV9zY2FsZV9iYXIocG9zaXRpb24gPSBjKDAsMCkpICsNCiAgIHRtX2xheW91dCh0aXRsZSA9ICJBdmVyYWdlIGNvc3Qgb2YgYW5pbWFsIHJlbGF0ZWQgaW5jaWRlbnRzIGJldHdlZW4gMjAwOSBhbmQgMjAyMCIsICANCiAgICAgICAgICAgIGZyYW1lID0gRkFMU0UsIGlubmVyLm1hcmdpbnMgPSBjKDAuMSwwLjEsMC4xLDAuMTUpKQ0KYGBgDQoNCg0KRmluYWxseSBsZXQncyBjaGFuZ2UgdGhlIGNvbG91ciBvZiBvdXIgbWFwIGFuZCBpbmNyZWFzZSB0aGUgY29udHJhc3QuIENob29zZSBhIGNvbG91ciBmcm9tIFtSIENvbG91cnNdKGh0dHBzOi8vd3d3LnItZ3JhcGgtZ2FsbGVyeS5jb20vMzgtcmNvbG9yYnJld2Vycy1wYWxldHRlc19maWxlcy9maWd1cmUtaHRtbC90aGVjb2RlLTEucG5nKS4NCg0KYGBge3J9DQp0bV9zaGFwZShtc29hX2xmYikgKyANCiAgdG1fcG9seWdvbnMoY29sID0gImNvc3RfcGVyX2luY2lkZW50IiwgdGl0bGUgPSAiQ29zdCBwZXIgSW5jaWRlbnQgKMKjKSIsDQogICAgICAgICAgICAgIGxhYmVscyA9IGMoIjAgLSAyMDAiLCAiPjIwMCAtIDQwMCIsICI+NDAwIC0gNjAwIiwgIj42MDAgLSA4MDAiLCAiPjgwMCAtIDEsMDAwIiwgDQogICAgICAgICAgICAgICAgICAgIj4xLDAwMCAtIDEsMjAwIiksIHBhbGV0dGUgPSAiQmx1ZXMiLCBjb250cmFzdCA9IDEpICsgDQogIHRtX3NoYXBlKGxvbmRvbl9ib3VuZGFyeSkgKyB0bV9ib3JkZXJzKGNvbCA9ICJibGFjayIpICsNCiAgdG1fc2NhbGVfYmFyKHBvc2l0aW9uID0gYygwLDApKSArDQogICB0bV9sYXlvdXQodGl0bGUgPSAiQXZlcmFnZSBjb3N0IG9mIGFuaW1hbCByZWxhdGVkIGluY2lkZW50cyBiZXR3ZWVuIDIwMDkgYW5kIDIwMjAiLCAgDQogICAgICAgICAgICBmcmFtZSA9IEZBTFNFLCBpbm5lci5tYXJnaW5zID0gYygwLjEsMC4xLDAuMSwwLjE1KSkNCmBgYA0KDQoNCg0KRmluYWxseSwgc2F2ZSB5b3VyIG1hcCBhcyBhbiBSIG9iamVjdCBhbmQgZXhwb3J0IGl0Lg0KDQpgYGB7cn0NCmF2ZXJhZ2VfY29zdCA8LSB0bV9zaGFwZShtc29hX2xmYikgKyANCiAgdG1fcG9seWdvbnMoY29sID0gImNvc3RfcGVyX2luY2lkZW50IiwgdGl0bGUgPSAiQ29zdCBwZXIgSW5jaWRlbnQgKMKjKSIsIHBhbGV0dGUgPSAiQmx1ZXMiLCBjb250cmFzdCA9IDEpICsgDQogIHRtX3NoYXBlKGxvbmRvbl9ib3VuZGFyeSkgKyB0bV9ib3JkZXJzKGNvbCA9ICJibGFjayIpICsNCiAgdG1fc2NhbGVfYmFyKHBvc2l0aW9uID0gYygwLDApKSArDQogICB0bV9sYXlvdXQodGl0bGUgPSAiQXZlcmFnZSBjb3N0IG9mIGFuaW1hbCByZWxhdGVkIGluY2lkZW50cyBiZXR3ZWVuIDIwMDkgYW5kIDIwMjAiLCAgDQogICAgICAgICAgICBmcmFtZSA9IEZBTFNFLCBpbm5lci5tYXJnaW5zID0gYygwLjEsMC4xLDAuMSwwLjE1KSkNCmBgYA0KYGBge3IsIGV2YWw9RkFMU0V9DQp0bWFwX3NhdmUoYXZlcmFnZV9jb3N0LCAib3V0cHV0L21hcHMvYXZlcmFnZV9jb3N0X21zb2EucG5nIiwgd2lkdGggPSA4LCBoZWlnaHQgPSA1KQ0KYGBgDQoNCllvdSBjYW4gYWxzbyB2aWV3IHlvdXIgY2hvcm9wbGV0aCBhcyBhbiBpbnRlcmFjdGl2ZSBtYXAuIEl0IGhlbHBzIHRvIGFkZCBhbiBgYWxwaGFgIGFyZ3VtZW50IHRvIGNoYW5nZSB5b3VyIG1hcCdzIHRyYW5zcGFyZW5jeS4NCg0KYGBge3J9DQp0bWFwX21vZGUoInZpZXciKQ0KdG1fc2hhcGUobXNvYV9sZmIpICsgDQogIHRtX3BvbHlnb25zKGNvbCA9ICJjb3N0X3Blcl9pbmNpZGVudCIsIHRpdGxlID0gIkNvc3QgcGVyIEluY2lkZW50ICjCoykiLCBwYWxldHRlID0gIkJsdWVzIiwgY29udHJhc3QgPSAxLCBhbHBoYSA9IDAuNSkgKyANCiAgdG1fc2hhcGUobG9uZG9uX2JvdW5kYXJ5KSArIHRtX2JvcmRlcnMoY29sID0gImJsYWNrIikgDQpgYGANCg0KIyBSZWNvbW1lbmRlZCByZXNvdXJjZXMNCg0KW0dlb2NvbXB1dGF0aW9uIHdpdGggUl0oaHR0cHM6Ly9nZW9jb21wci5yb2JpbmxvdmVsYWNlLm5ldC9pbmRleC5odG1sKSAgDQoNCltTaW1wbGUgRmVhdHVyZXMgZm9yIFJdKGh0dHBzOi8vci1zcGF0aWFsLmdpdGh1Yi5pby9zZi9pbmRleC5odG1sKSAgDQoNCltTcGF0aWFsIERhdGEgU2NpZW5jZSB3aXRoIFJdKGh0dHBzOi8vd3d3LnJzcGF0aWFsLm9yZy8pICANCg0KW0NyZWF0aW5nIGRlbW9ncmFwaGljIG1hcHMgaW4gUiB3aXRoIHRtYXAgcGFja2FnZXNdKGh0dHA6Ly93d3cuemV2cm9zcy5jb20vYmxvZy8yMDE4LzEwLzAyL2NyZWF0aW5nLWJlYXV0aWZ1bC1kZW1vZ3JhcGhpYy1tYXBzLWluLXItd2l0aC10aGUtdGlkeWNlbnN1cy1hbmQtdG1hcC1wYWNrYWdlcy8pDQo=